随着项目的发展,项目目录会越来越大,各种库也会越来越多,会直接导致Webpack的构建效率极低,比如下面的例子:

1
2
3
4
5
6
7
$  webpack
Hash: 6aa4a418e100b6563347
Version: webpack 3.5.5
Time: 20199ms
Asset Size Chunks Chunk Names
index.js 4.27 MB 0 [emitted] index
+ 395 hidden modules

可以看到,在这个项目中有大量的模块,构建一次的时间长达20秒,这显然是不可接受的。

慢在哪里

有很多工具提供了可视化的分析,如Webpack-bundle-analyzerwebpack-chartwebpack-analyse
以Webpack-bundle-analyzer为例,它提供了一个下图所示的图表,展示了引入的所有模块的大小、路径等信息,可以针对性的做出优化。

Webpack-bundle-analyzer
Webpack-bundle-analyzer

使用上也很简单:

1
2
// 全局安装:
yarn global add webpack-bundle-analyzer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// webpack.config.js 配置
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerHost: '127.0.0.1',
analyzerPort: 8888,
reportFilename: 'report.html',
defaultSizes: 'parsed',
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
logLevel: 'info'
})
]

运行webpack命令,会自动在浏览器中打开http://127.0.0.1:8888/页面,展示可视化图表。

具体优化

1. 使用DllPlugin拆分模块

开发过程中,我们经常需要引入大量第三方库,这些库并不需要随时修改或调试,我们可以使用DllPluginDllReferencePlugin单独构建它们。
具体使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: {
vendor: [
'axios',
'vue-i18n',
'vue-router',
'vuex'
]
},
output: {
path: path.resolve(__dirname, '../static/'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'build', '[name]-manifest.json'),
name: '[name]_library'
})
]
}

执行webpack命令,build目录下即可生成 dll.js 文件和对应的 manifest 文件,使用 DLLReferencePlugin 引入:

1
2
3
4
5
6
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./build/vendor-manifest.json')
})
]

2. 使用 externals 通过CDN引入第三方库

Webpack提供了 externals 的方式来引入第三方库,我们可以在 HTML 文件中直接使用 script 标签的形式来引入:

1
2
<script src="//cdn.bootcss.com/react.min.js"></script>
<script src="//cdn.bootcss.com/react-dom.js"></script>

在 Webpack 中如下配置即可:

1
2
3
4
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}

和 dll 的打包方式相比,主要有以下几点区别:

  1. 并非所有依赖库都提供了打包好的生产环境的文件,对于这种只能通过npm来引入的库, externals 无能为力。
  2. 部分依赖库中会存在循环依赖的现象,在一些 React 相关的库中尤为明显,使用 externals 处理会造成路径混乱无法识别。
  3. 使用dll的方式打包好的静态文件在生产环境中需要额外处理,同步到build目录中。(可以使用 CopyWebpackPlugin 等插件)。

3. 使用 happypack 开启多线程构建

HappyPack可以将原有的 webpack 对 loader 的执行过程,从单一进程的形式扩展为多进程的模式,从而加速代码构建。使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module: {
loaders: [{
test: /\.less$/,
loader: ExtractTextPlugin.extract(
'style', path.resolve(__dirname, './node_modules', 'happypack/loader') + '?id=less'
)
}]
},
plugins: [
new HappyPack({
id: 'less',
loaders: ['css!less'],
threadPool: happyThreadPool,
cache: true,
verbose: true
})
]

更多配置可以参考文档:https://github.com/amireh/happypack

4. 增强代码压缩工具

Webpack默认提供的UglifyJS插件速度很慢,可以使用webpack-parallel-uglify-plugin替换。配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const os = require('os');

new ParallelUglifyPlugin({
workerCount: os.cpus().length,
cacheDir: '.cache/',
uglifyJS: {
output: {
comments: false
},
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
},
sourceMap: true,
mangle: true
}
})

5. 缩小文件搜索范围

Node.js的模块的载入及缓存机制如下:

  1. 载入内置模块
  2. 载入文件模块
  3. 载入文件目录模块
  4. 载入node_modules里的模块
  5. 自动缓存已载入模块

如果模块名不是路径,也不是内置模块,Node将试图去当前目录的node_modules文件夹里搜索。如果当前目录的node_modules里没有找到,Node会从父目录的node_modules里搜索,这样递归下去直到根目录。

我们可以对搜索过程进行一些优化,比如可以直接指定node_modules的路径:

1
2
3
4
5
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')]
}
};

6. Webpack3 新功能: Scope Hoisting

在Webpack3.0 版本中,提供了一个新的功能:Scope Hoisting,又译作“作用域提升”。在Webpack2中,打包后的文件里每个模块都会被包装在一个单独的闭包中,这些闭包会导致JS执行速度变慢,Scope Hoisting则可以将所有模块打包进一个大的闭包中。只需在配置文件中添加一个新的插件,就可以让 Webpack 打包出来的代码文件更小、运行的更快:

1
2
3
4
5
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
}

7. 合理配置 babel-loader

在balel的官方文档中有一句话:babel-loader is slow!
我们在babel的配置中,也要尽量注意这一点。
比如,使用 /\.js$/来匹配文件的话,可能会将node_modules中的文件一起处理,我们需要使用exclude、include等尽可能准确的来指定需要转换的内容。
我们还可以开启babel的缓存配置(cacheDirectory)来提升一倍以上的效率,配置如下:

1
2
3
test: /\.js$/,
loader: 'babel-loader?cacheDirectory=true',
exclude: /node_modules/