0%

webpack构建速度优化

本文介绍了几种优化 webpack 构建速度的方式。

多进程

优化构建速度,可以通过多进程/多线程同时构建的方式来进行优化,目前主要有 Happypack / thread-loader / parallel-webpack 这几个解决方案。本文主要介绍了前两种方案。

优化前的构建速度:

使用 Happypack

Github - amireh/happypack

原理

happypack 会创建一个线程池,线程池会将构建任务里面的模块分配到不同线程上,各线程会去处理这个模块及其依赖。处理完成后,会将处理完成的资源传输给 happypack 的主进程,然后完成整个构建任务。参见下图:

安装及使用

安装:

1
npm i happypack -D

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// webpack.config.js
const HappyPack = require('happypack')

module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: 'happypack/loader'
},
// ...
]
},
plugins: [
new HappyPack({
loaders: ['babel-loader']
})
]
}

使用后构建速度:

可以看到,happypack 开启了三个线程进行构建,大幅度地缩减了构建时间。

thread-loader

Github - webpack-contrib/thread-loader

原理

thread-loader 的原理与 happypack 类似,也是创建一个线程池,线程池会将构建任务里面的模块分配到不同线程上,各线程会去处理这个模块及其依赖。处理完成后,会将处理完成的资源传输给主进程,然后完成整个构建任务。

安装及使用

安装:

1
npm i thread-loader -D

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// webpack.config.js
const HappyPack = require('happypack')

module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: [
'thread-loader', // 默认开启 (cpu个数 - 1) 个线程
'babel-loader'
]
},
// ...
]
},
// ...
}

使用后构建速度:

可以看到,thread-loader 使用后也大幅度地缩减了构建时间。

多进程并行压缩

并行压缩代码,有两种方式:

  1. 使用 parallel-uglify-plugin 插件
  2. 使用 terser-webpack-plugin 开启 parallel 参数

使用 parallel-uglify-plugin 插件

Github - gdborton/webpack-parallel-uglify-plugin

1
2
3
4
5
6
7
// webpack.config.js
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
module.exports = {
plugins: [
new ParallelUglifyPlugin()
],
}

使用 terser-webpack-plugin 开启 parallel 参数

Github - webpack-contrib/terser-webpack-plugin

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 布尔值或开启的线程数,默认值为 (cpu个数 - 1)
}),
],
},
};

缓存

缓存对于首次构建并没有什么帮助,但是,可以充分利用缓存,提升二次构建的速度。
开启缓存有三种方式:

  1. babel-loader 开启缓存 – bebal 转换 js 的时候的缓存
  2. terser-webpack-plugin 开启缓存 – 代码压缩阶段的缓存
  3. 使用 hard-source-webpack-plugin – 模块转换阶段的缓存

缓存的存储位置位于 ./node-modules/.cache

babel-loader 缓存

设置 babel-loadercacheDirectory 选项为 true,即可开启缓存:

1
2
3
4
5
6
7
8
9
10
11
module: {
rules: [
// ...
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
]
}

第一次构建:

第二次构建:

可以看到,开启缓存后,有小幅度优化。

terser-webpack-plugin 缓存

设置 TerserPlugincache 选项为 true,即可开启缓存:

1
2
3
4
5
6
7
8
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
cache: true, // 开启缓存
}),
],
},

第一次构建:

第二次构建:

可以看到,开启缓存后,优化效果明显。

hard-source-webpack-plugin 缓存

添加插件 hard-source-webpack-plugin 即可开启缓存:

1
2
3
4
plugins: [
// ...
new HardSourceWebpackPlugin()
]

第一次构建:

第二次构建:

可以看到,开启缓存后,优化效果明显。

预编译资源模块

  1. npm run build 构建之前,先把一些基础包和业务包打包成一个文件
  2. 使用 DllPlugin 进行分包
  3. DllPlugin 把基础包打包成一个文件,然后全局暴露一个变量,需要在 html 引入进来
  4. DllPlugin 中的 options[name] 要和 output[library] 对应起来,不然会报一个引用错误
  5. 通过引入 add-asset-html-webpack-plugin 把已经打包的 js,通过 html 引入。注意不要和 SpeedMeasurePlugin 同时使用,会出错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// webpack.dll.js -- 打包成 dll
const path = require('path')
const webpack = require('webpack')

module.exports = {
entry: {
library: ['react', 'react-dom'] // 要打包的库
},
output: {
filename: '[name].dll.js',
path: path.join(__dirname, 'build/library'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: './build/library/[name].manifest.json'
})
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// webpack.config.js -- 使用 dll
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
// ...
plugins: [
new webpack.DllReferencePlugin({ // 通过引用 .dll.js 中打包生成的 manifest,json 文件,来引用打包的库文件
manifest: require('./build/library/library.manifest.json')
}),
new HtmlWebpackPlugin(),
new AddAssetHtmlPlugin({ // 把 .dll.js 文件添加到 html 中。注意!AddAssetHtmlPlugin 必须在 HtmlWebpackPlugin 后使用
filepath: path.resolve('./build/library/*.dll.js')
})
]
}

使用前:

使用后:


可以看到,构建体积明显小了很多。

缩小构建目标

可以通过减少构建的模块,来使构建速度得到一定的优化,比如 babel-loader 不解析 node_modules

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: 'node_modules'
}
]
}
}

减少文件搜索范围

webpack 在解析文件时,会逐层往上找,比如 require('react') ,会先搜索当前目录下是否存在 react.js/.json 等文件,没找到的话,再到 node_modules 及上层目录中找,较为耗时。

因此,可以指定搜索范围来减少这种不必要的搜索。主要有以下4点:

  1. 优化 resolve.modules 配置:指定模块查找位置
  2. 优化 resolve.mainFileds 配置:模块入口文件字段
  3. 优化 resolve.extensions 配置:缺少扩展名时,尝试补齐的文件类型
  4. 使用 alias:模块位置

如:

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
module.exports = {
resolve: {
alias: {
'react': path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom/umd/react-dom.production.min.js'),
},
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
extensions: ['.js', '.jsx'],
mainFields: ['main']
}
}