# 构建速度优化
使用 webpack 构建项目时,可以考虑一下构建速度的优化:
# 构建过程提速策略
# 1、不要让 loader 做太多事情
loader 功能很强大,但是也需要合理的使用,不要让 loader 做一些不必要的工作。
# babel-loader
https://v4.webpack.js.org/loaders/babel-loader/
- 在使用
babel-loader
时,要确保转译尽可能少的文件。当使用/\.m?js$/
来匹配需要转译的文件类型时,也许会去转译node_modules
目录或者其他不需要的源代码。对此,可以使用exclude
来避免不必要的转译. - 另外也可以通过设置
cacheDirectory
选项为true
,将babel-loader
提速至少两倍。这会将转译的结果缓存到文件系统中,之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true
}
}
}
];
# 2、处理第三方库
第三方库以 node_modules
为代表,它们庞大得可怕,却又不可或缺。
处理第三方库的姿势有很多,其中,Externals
不够聪明,一些情况下会引发重复打包的问题;而 CommonsChunkPlugin
每次构建时都会重新构建一次 vendor;出于对效率的考虑,这里为大家推荐 DllPlugin
。
DllPlugin
是基于 Windows 动态链接库(Dynamic-link library)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。
# DllPlugin
用 DllPlugin
处理文件,要分两步走:
- 基于 dll 专属的配置文件,打包 dll 库
- 基于
webpack.config.js
文件,打包业务代码
以一个基于 React 的简单项目为例,我们的 dll 的配置文件可以编写如下:
// webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 由依赖的库组成的数组
vender: ['prop-types', 'babel-polyfill', 'react', 'react-dom', 'react-router-dom']
// 也可以分开打包
// babel: ['babel-polyfill'],
// react: ['prop-types', 'react', 'react-dom', 'react-router-dom']
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
library: '[name]_[hash]'
},
plugins: [
new webpack.DllPlugin({
// DllPlugin 的 name属性需要和 Libary 保持一致
name: '[name]_[hash]',
path: path.join(__dirname, 'dist', '[name]-mainfest.json'),
// context 需要和 webpack.config.js 保持一致
context: __dirname
})
]
};
编写完成之后,运行这个配置文件,dist 文件夹里会出现这样两个文件:
vendor-manifest.json
vendor.js
vendor.js 是第三方库打包的结果。vendor-manifest.json,则用于描述每个第三方库对应的具体路径,下面截取一段看一下内容大概是什么样子:
{
"name": "vendor_397f9e25e49947b8675d",
"content": {
"./node_modules/core-js/modules/_export.js": {
"id": 0,
"buildMeta": {
"providedExports": true
}
},
"./node_modules/prop-types/index.js": {
"id": 1,
"buildMeta": {
"providedExports": true
}
},
...
}
}
第一步,打包第三方库已经完成,下面来进行第二步:在 webpack.config.js
中配置 dll。
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
// 编译入口
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, 'dist/'),
filename: '[name].js'
},
// dll 相关配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// mainfest 就是第一步中打包出来的 json 文件
mainfest: require('./dist/vendor-mainfest.json')
})
]
};
一次基于 dll 的 webpack 构建过程优化,便大功告成了!
# 3、thread-loader(将 loader 由单进程转为多进程)
webpack 是单线程的,多个任务需要排队依次执行。thread-loader
可以开启一个线程池,让其他比较耗时的 loader 在这个线程池中工作。
它的用法就是把 thread-loader
放在其他 loader 前面就可以了,看下面的例子:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve('src'),
use: [
{
loader: 'thread-loader',
// loaders with equal options will share worker pools
options: {
// the number of spawned workers, defaults to (number of cpus - 1) or
// fallback to 1 when require('os').cpus() is undefined
workers: 2
}
}
// your expensive loader (e.g babel-loader)
"babel-loader"
]
}
]
}
};
babel-loader
功能强大,但相对耗时,例子中,将 thread-loader
添加在 babel-loader
之前,把 bable-loader
放在单独的线程池工作,提升了编译速度。