Webpack优化 | 快一点,再快一点
Posted 点融黑帮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Webpack优化 | 快一点,再快一点相关的知识,希望对你有一定的参考价值。
最近项目的开发环境升级,
引入了 js 构建工具 webpack,生产力大大提高。
开心了两个月后,随着项目代码逐日增多,
我们遇到了一个问题:构建太慢了。
慢到什么程度呢,
开发时第一次启动要40秒左右,
生产构建则需要1分多钟甚至2分钟。
看着似乎不是特别慢,
你可以试着出了 bug 后和 pm, 测试,设计小姐姐注视2分钟…
李梦南 1分钟前
人生苦短, 我用 python,一刻千金。
大好时光怎么可以把时间浪费在构建上呢?
so,快一点,再快一点!
☞分析
我们是多页面应用,拥有很多入口,构建工具需要去遍历指定文件夹内的指定 js 文件并生成对应的 html 文件出来,然后我们有 rename 的需求,即生成的 html 的文件名可以和 js 文件名不一致。
于是我们在 js 里提供了一个特定语句:
// card/index.js
@Entry({
filename: 'card_list.html'
})
然后在编译时通过 babel parse 出 ast 后拿到 filename 的值,再交给 webpack 。
这样的好处是,开发时能够直观的设置文件名称,后续维护的时候也很直观的知道最终输出的文件名称。时间上的消耗在项目初期代码不是很多,js 文件不是很大的时候也还是可以接受的。但当 js 文件越来越多,越来越大,就有点让人头疼了。而且为了满足就近原则,我们会把公用的业务逻辑抽出来放在同文件夹内,就会浪费很多时间在这些非 entry 文件上。
☞结论
ast 好用,但是有点花时间。
☞加速
秉着谁拖后腿就干掉谁的原则,首先 babel parse to ast 这个我们就别要了,找个其他能满足我们需求的办法,有没有呢?当然有,而且就近在眼前。用过 gulp 的同学肯定都见过这行代码:gulp.src('client/templates/*.jade') gulp api,这里的匹配规则使用的node-glob。glob 在匹配特定文件时非常方便,且支持ignore,用来匹配我们的 entry 实在太合适不过了。下面是获取 entries 的例子:
function getEntries(dir = 'src/pages') {
const entries = {}
const root = resolve(dir) + '/'
const files = glob.sync(root + '**/*.js', {
ignore: [
'**/view.js',
'**/_*/**',
'_*.js'
]
})
files.forEach((file) => {
entries[
file
.replace(root, '')
.replace('/index.js', '')
.replace(/\//g, '_')
.replace('.js', '')
] = file
})
return entries
}
这里我们通过特定的类似 sass 中的做法约定非 entry 文件使用_前缀以忽略。并根据文件的路径生成 {dir_filename: absolutePath} 这样的 entries 给 webpack。同时,为了满足 rename 的需求,允许提供一个 router.js 文件来定义需要 rename 的 entry,类似:
// router.js
module.exports = {
rename: require.resolve(filePath)
}
然后通过下面的方法对原始 entry 和 router 进行 merge:
function mergeEntries(entry, router) {
const reducer = (prev, [key, val]) => {
prev[val] = key
return prev
}
const reverse = source => Object
.entries(source)
.reduce(reducer, {})
return reverse(reverse(Object.assign({}, entry, router)))
}
然后把最终的 entries 交给 webpack 就 ok 了。
原来的 webpack 使用的是 v1.x 版本,最近 webpack 已经发布了3.x 的版本,更加的 powerful。于是我便将依赖也升级到最新的 v3.5.5,然后傻眼了。在多 entry 的情况下,webpack3 与最新的 html-webpack-plugin 配合起来并不默契。会导致构建时间几何翻倍,详细看可以看这个issue(https://github.com/jantimon/html-webpack-plugin/issues/724)。
在这个 issue 里我发现了个有意思的东西,就是 html-webpack-plugin 的 v1.7.0 版本简直不要太快,降级到 v1.7.0 后构建速度果然飞起来了,比起 2.x 都要快很多。但是它不支持chunksSortMode: dependency ,意味者当你在生产构建时使用了CommonsChunkPlugin后将无法正确的排列 js 的引入顺序。不过好在它支持 function 参数,且我们的 chunks 是固定的,便可以通过下面这样的方式使用:
Object.keys(config.entry).map((file) => {
const chunks = ['manifest', 'vendor', 'commons', file]
return new HtmlWebpackPlugin({
chunks,
filename: file + '.html',
template: resolve('index.html'),
inject: 'head',
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: (a, b) => chunks.indexOf(a.names[0]) - chunks.indexOf(b.names[0])
})
})
到这里,其实速度已经可以了,但不容易满足的我岂会到这里就结束。
之前曾在淘宝 FED 看过一篇讲happypack的文章,happypack 是 webpack 的一个插件,目的是通过多进程模型,来加速代码构建,具体原理可以看这里。
happpack 的使用方法很简单:
// webpack config
const HappyPack = require('happypack')
const os = require('os')
const config = {
module: {
rules: [
{
test: /\.js$/,
loader: 'happypack/loader?id=js'
}
]
},
plugins: [
new HappyPack({
id: 'js',
// 创建一个线程池共享给所有的 HappyPack 实例
threadPool: HappyPack.ThreadPool({ size: os.cpus().length }),
loaders: [{
path: 'babel-loader',
query: {
cacheDirectory: true
}
}]
})
]
}
至此为止,本次对构建速度的优化就结束了,效果如何呢?开发时 1st 构建可以控制在 10s 左右,生产构建在 uglify + gzip 的情况下 1st 构建:
有 cache 的情况下改动单个文件:
可以说是开上飞机啦!
结语
其实 webpack 还有一些优化空间
比如 dllPlugin
不过目前的速度我已经很满足了
等到遇到下一个瓶颈时再去研究吧
如果你有更好的优化方法
欢迎分享评论
点击回顾往期精彩内容
以上是关于Webpack优化 | 快一点,再快一点的主要内容,如果未能解决你的问题,请参考以下文章