webpack 优化从两个方面入手。
一、优化开发体检。包括优化构建速度和优化使用体验
二、优化输出质量,包括减少用户能感知到的加载时间(首屏加载时间)和提升流畅度(提升代码性能)。参考链接优化输出质量
项目背景:我目前的项目,功能巨多,业务庞大。打包上面的确有很多不合理的地方以致于打包时间最长有过20分钟的情况。平均正常也得800-900秒。在另一个同事的积极推动建议下,开启了打包优化之旅。我的主要目标是优化构建速度。所以这块验证的比较多。其他的知识点算是总结了。我主要参考的是深入浅出webpack这本书。
优化构建速度
是按我自己项目中的构建优化时间从大到小排列的。
-
通过 Node.js API 启动 Webpack,构建时间减少了一半。减少到400-500秒
无意中发现的方式。因为本身项目比较老,没有用脚手架工具。参考用脚手架create-react-app工具搭建的项目的配置。发现他们启动的方式是通过Node.jsAPI启动的。当时做的时候,不知道命名,是看了参考书,才知道原来有这个定义。参考 通过 Node.js API 启动 Webpack
书上这样介绍:通过 API 去调用并执行 Webpack 比直接通过可执行文件启动更加灵活,可用在一些特殊场景(没有介绍具体的使用场景,但是发现脚手架都是这样的用的)
总结:webpack执行的两种方式:- Webpack 其实是一个 Node.js 应用程序,它全部通过 JavaScript 开发完成。 在命令行中执行 webpack 命令其实等价于执行 node ./node_modules/webpack/bin/webpack.js。
// package.json "scripts": { "start": "webpack", },
- 通过 Node.js API 启动 Webpack。
const config = require('webpack.config.js') const webpack = require('webpack') const compiler = webpack(config) compiler.run((err, stats) => { // console.log(err, stats) })
这块没有理解为啥通过Node.jsAPI的方式会快这么多。猜测应该是直接webpack的时候,做了一些判断啥,然后才走的webpack-cli。然而Node.jsAPI是直接走到webpack-cli。代码少了一部分。因而时间少。不过没有证实,也不确定少走什么代码,时间这么大的差异。(如果有知道的,欢迎补充,我会再更新。如果我后续看懂了源码,也会再补充。)
使用 ParallelUglifyPlugin 多进程压缩,构建时间减少了200s左右,平均值在250秒
代码构建的时候,需要先经过loader处理,然后再压缩。由于压缩 JavaScript 代码需要先把代码解析成用 Object 抽象表示的 AST 语法树,再去应用各种规则分析和处理 AST,导致这个过程计算量巨大,耗时非常多。这里借鉴使用 HappyPack中介绍过的多进程并行处理的思想引入到代码压缩中。(HappyPack使用 ,在我的项目中,没有减少时间,反而增加了几秒。所以排列在最后面。肯定有他的好处,但是要慎用,因为开启进程也会耗费时间)使用此项配置,时间减少到300秒左右。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports ={
entry: 'main.js',
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].chunk.js'
},
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
uglifyJS: {
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
]
}
-
修改压缩图片的loader配置。减少130秒左右,到120秒
原来loader配置中用了压缩图片的功能,image-webpack-loader,导致时间变长。去掉后时间明显减少。压缩倒是很大。由120kb的图片可以压缩到40kb。但是时间很久。一开始去掉了此配置,换成了 imagemin-webpack-plugin插件做压缩。压缩质量设置成一样的,都是65-90。时间却省了不少。基本跟现在一样。后来修改了loader的原有配置,只剩下quality一项。时间也会减少很多。结论是:两个用哪个都行。image-webpack-loader的配置挺重要的。代码配置如下
// 原来比较慢的配置
{
test: /\.(jpg|png|jpeg|gif|pdf|mp3|aac)$/,
loaders: [
'file-loader',
{
loader: 'image-webpack-loader',
query: {
progressive: true,
optimizationLevel: 7,
interlaced: false,
pngquant: {
quality: [0.65, 0.9],
speed: 4
}
}
}
]
},
// 去掉某些配置后,压缩时间减少了
{
test: /\.(jpg|png|jpeg|gif|pdf|mp3|aac)$/,
loaders: [
'file-loader',
{
loader: 'image-webpack-loader',
query: {
pngquant: {
quality: [0.65, 0.9],
}
}
}
]
},
// 用插件的配置 这个也挺快的
new ImageminPlugin({
pngquant: {
quality: '65-90'
}
}),
-
缩小文件搜索范围(只采用了第一项,整体打包时间到50s左右)
- 优化 loader 配置。减少70秒左右。
module.exports = { module: { rules: [ { // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能 test: /\.js$/, // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启 use: ['babel-loader?cacheDirectory'], // 只对项目根目录下的 src 目录中的文件采用 babel-loader include: path.resolve(__dirname, 'src'), }, ] }, };
- 优化 resolve.modules 配置(未使用)
此项也能减少50秒左右。但是此项引发了我项目的bug。参考一个webpack构建速度优化误区。我的项目有用到websocket。按照这篇文章的意思,我觉得可能是最外层的node-modules目录下和插件目录下都存在一个组件。然后修改了此项,改变了组件的引用地址。本来应该用组件下的node-modules里面的某项,结果用了最外层的。由于版本不一致,导致的错误。所以此项没有真实用到项目里面。
module.exports = { resolve: { // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 // 其中 __dirname 表示当前工作目录,也就是项目根目录 modules: [path.resolve(__dirname, 'node_modules')] }, };
- 优化 resolve.mainFields 配置(未使用)
减少20-30秒,但是我的项目是允许在浏览器端的。也就是target:web。如果改成以main为先,项目也存在错误。
resolve.mainFields 的默认值和当前的 target 配置有关系,对应关系如下:
当 target 为 web 或者 webworker 时,值是 ["browser", "module", "main"]
当 target 为其它情况时,值是 ["module", "main"]// 我原本的是这样 mainFields: [ 'browser', 'jsnext:main', 'main' ] // 为了缩小查询的步骤 建议改成。但是这种改法,在我们做网站的这种项目中,都是运行在浏览器端的。所以不建议。会出错 mainFields: [ 'main', 'browser', 'jsnext:main', ]
- 优化 resolve.alias 配置(未使用)
减少30-40秒。原理是直接引用压缩好的文件,省去了打包。我把react-dom也放到这里,优化时间会多一点,其他的看不太出来。但是react-dom 用了后就报错了。于是也没有真正使用。 - 优化 resolve.extensions 配置(未使用)
在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在。resolve.extensions
用于配置在尝试过程中用到的后缀列表,默认是: extensions: ['.js', '.json'] 。这项项目中本来就有。所以没有用。但是通过这项,我觉得应该在平时书写代码的时候加上后缀,也会加快读取。 - 优化 module.noParse 配置(未使用)
通过配置 module.noParse 忽略对 没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。跟alias类似。忽略掉react-dom,报一样的错误。也没有再用了。
- 优化 loader 配置。减少70秒左右。
-
使用 DllPlugin,这项没验证。
DllPlugin 主要是webpack内置的插件。就是先把某些 不用变动的包 单独打包出去,然后再静态引入,这是我们再配置webpack告诉webpack 哪些包不用再编译了,这样就能很大程度上提高我们的编译速度。 node-module里面的很多我们需要用的包,以vue项目为例,比如vue echart element等,就用这个打包我们需要用的。里面还有很多是webpack的依赖,那些不需要我们打包。-
1.建立一个webpack.dll.js的文件 里面放所有提前编译好的文件
let path = require('path') let {CleanWebpackPlugin} =require('clean-webpack-plugin') let webpack = require('webpack') module.exports = { mode: 'production', entry: { vue:['vue'] }, output: { filename: 'dll_[name].js', path: path.resolve(__dirname, 'dist'), library:'dll_[name]' }, plugins: [ new CleanWebpackPlugin(), new webpack.DllPlugin({ name: 'dll_[name]', // name = library path:path.resolve(__dirname,'dist','manifest.json') }) ] }
-
2.package.json 中配置编译这些文件的命令
"scripts": { "dev": "webpack-dev-server --config webpack.config.dev.js", "build": "webpack --config webpack.config.dev.js", "dllVue": "webpack --config webpack.dll.js" },
此时 通过启动 npm run dllVue 即可把这些不用每次都编译的文件编译好
-
3.webpack.config.js 中配置 打包的时候 不去编辑以上打包好的文件
plugins: [ new webpack.DllReferencePlugin({ // 打包编译的时候,先去这个清单中查找 清单中没有再去进行打包 manifest:path.resolve(__dirname,'dist','manifest.json') }), new CleanWebpackPlugin({ // 告诉这个插件 不用清除这些文件 cleanOnceBeforeBuildPatterns:['**/*','!dll_*','!mani*'] }), ],
4.本地开发的时候要 查找 已经编译好的文件 这些文件是静态文件 所以要在devServer中设置contentBase
devServer: { port: 8999, // hot:true, // 查找到dist里面的文件 contentBase:'dist', <!--DevServer起服务后 文件都是在他的内存中的 然后如果要访问我们本地的文件的话 他是访问不到的 所以要用这个 contentBase指向我们本地的文件夹。也就是把contentBase对应的文件,作成他的服务中的静态文件,这样通过服务就可以访问到文件。--> },
- 5.index.html页面要引入这个文件
<script src="./dll_vue.js"></script>
-
使用 HappyPack(未使用,使用了时间没减少)
多进程处理文件,加快速度的。但是开启进程也需要时间。慎重使用。
happyPack已不再维护,官网推荐的是thread-loader.通过在loader处理之前,拦截开启进程。使用过程时间未减少,反而增了几秒,没有 ParallelUglifyPlugin效果明显。(可能这个说法不准确,只是以我自己的项目为例显示出来的效果)
优化使用体验
- 使用自动刷新
- 开启模块热替换