1. 跟上技术的迭代
- Node、Npm、Yarn、Webpack都要保持最新的版本
2. 在尽可能少的模块上应用Loader
- 合理的使用exclude或者include的配置,来尽量减少loader被频繁执行的频率。当loader执行频率降低时,也会提升webpack的打包速度。比如:
module: {
rules: [{
test: /\.js$/,
include: path.resolve(__dirname, '../src'), // 只对src目录下的js文件做打包转译工作
// exclude: /node_modules/, // 如果你的js文件在node_modules里边,就不使用babel-loader了,因为它里边的代码都是些第三方代码,已经做好了转译的工作。
use: [{
loader: "babel-loader"
}]
}]
}
3. Plugin尽可能少并确保可靠
- 选择性能比较好的、官方推荐的或者社区认可的插件来使用。
- Plugin尽可能少的使用,减少冗余的代码分析。比如:(css代码压缩插件只在生产环境中配置,开发环境中没有必要使用)
- 开发环境无用插件的剔除。
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');
const prodConfig = {
mode: 'production', // 生产环境
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
}
module.exports = prodConfig;
4. resolve参数合理配置
- 通过合理优化resolve配置项,让webpack打包速度更快。
resolve: { // 新增配置项(如果想在模块里引入js文件,直接可省略后缀的话)
extensions: ['.js', '.jsx'], // 当引入一个其他目录下的模块的时候,首先会去找.js为后缀的文件,如果没有匹配到,再去找.jsx为后缀的文件
mainFiles: ['index', 'child'], // 当引入一个目录下的内容的时候,不知道具体要引入哪个文件,那么可以通过mainFiles配置先尝试去找index文件,如果没有index,再去找child文件
alias: {
child: path.resolve(__dirname, '../src/a/b/child') // 配置引入模块的别名
}
}
- extensions 配置了很多值时,那么在查找文件时,是会有性能损耗的。所以,一般我们在引入一些如js、jsx这种 逻辑型文件 时,我们才会把它配置到extensions的配置项里(像一些’.css’/’.png’等文件,强烈不建议配置在extensions里) 。这样我们的代码写起来也会方便些,性能方面也得到了合理的提升。
- mainFiles 配置很多值时,也会影响打包性能,所以在项目开发中,要视情况而定,不要盲目的配置mainFiles项。(一般项目不用配置)
- alias 当逻辑代码中引入的其他模块文件,所在层级较深时,比较适用,其他情况下,不建议使用。
5. 使用DllPlugin提升webpack打包速度
当我们的js代码里,引入第三方库 的时候,每次重新打包,都要重新分析所引入的第三方库代码,最终把他们打包到我们的项目之中。第三方库的代码都有个特点,那就是他们是不变的(不随业务逻辑变化而变化),所以我们可以把所有引入的第三方的代码都打包生成一个文件里,只在第一次打包时分析第三方代码,之后再执行打包时,直接用上次分析好的结果即可。
具体步骤如下:
- 我们对第三方库单独进行打包,生成一个打包文件。
- 使用 library 通过全局变量的形式把第三方库的所有代码暴露出去。
- 通过DllPlugin插件,来对我们暴露的代码做一个分析,生成一个manifest映射文件。
- 通过执行npm run build:dll 生成一个单独的第三方库的打包文件。(在scripts中配置)
- 在 webpack.common.js(公共的配置文件)文件中,引入DllReferencePlugin插件,去拿打包的映射文件。查找是否存在第三方库的映射关系。
如何结合全局变量和manifest.json映射文件进行webpack的配置??下方的例子:
使用DllReferencePlugin和DllPlugin进行配置:
我们单独配置一个第三方库的打包文件:webpack.dll.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash']
},
plugins: [
new webpack.DllPlugin({
name: '[name]', // 用DllPlugin对库进行分析
path: path.resolve(__dirname, '../dll/[name].manifest.json') // 把库里边第三方的一些映射关系,放到name.manifest.json文件下
})
],
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'), // 把第三方库代码打包到dll文件夹下
library: '[name]' // 暴露全局变量
}
}
- library 通过全局变量的形式把第三方库的所有代码暴露出去
scripts配置:package.json文件
"scripts": {
"build": "webpack --config ./build/webpack.prod.js",
"build:dll": "webpack --config ./build/webpack.dll.js"
}
执行打包第三方库文件:npm run build:dll
webpack.common.js中的插件配置:
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
plugins: [
new AddAssetHtmlWebpackPlugin({ // 为页面引入打包好的第三方库代码文件
filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
}),
new webpack.DllReferencePlugin({ // 查找第三方库的映射关系
manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
})
]
}
- webpack内置的DllReferencePlugin插件的作用主要是:在执行webpack打包时,对我们引入的第三方库,它会先去dll下的manifest映射文件中查找第三方模块的映射关系,如果能找到映射关系,那么它就会认为,引入的第三方库,没有必要再次进行打包了, 可以直接去全局变量里拿; 如果发现引入的第三放库的映射关系不在manifest映射文件里,那么它就会再从node_modules里取出该模块,然后再进行打包到我们的源代码里边。
再次打包 npm run build 我们会发现:
不配置DllReferencePlugin插件时:打包速度基本稳定在1000ms
配置DllReferencePlugin插件之后:打包速度基本稳定在600ms
实际上,在webpack配置大型复杂项目过程中,DllPlugin和DllReferencePlugin用的还是比较多的。
在webpack做打包的时候,我们就可以结合全局变量、以及我们生成的manifest映射文件,然后来对我们的源代码进行分析,一旦发现你使用的第三方库的内容是在vendors.dll.js里边,那么它就会直接使用vendors.dll.js里的内容了。就不会再去node_modules里引入我们的模块了。
6. 控制包文件大小
在我们的项目开发中,经常会引入一些没有用到的模块,引入之后,其实我们并没有使用这些模块。这个时候,如果你没有配置tree-shaking,就会导致在打包的过程中有很多冗余的代码。那么这些冗余的代码,实际上就会拖累webpack的打包速度。所以,在我们做打包的时候,对于一些冗余的代码,我们可以通过 tree-shaking(查看详解) 把它去除掉。(或者直接不去引用它)这样我们就可以控制webpack打包生成文件的大小,从而提升打包速度。
也可以通过splitChunksPlugin插件,对代码进行拆分,把一个大的文件,拆分成几个小的文件,做webpack的打包处理,这样也可以提高webpack的打包速度。
7. thread-loader, parallel-webpack, happypack 多进程打包
webpack默认是基于node.js来运行的,所以它是一个单进程的打包过程。我们也可以借助webpack里的多进程来帮我们提升打包速度
如:thread-loader, parallel-webpack, happypack 多进程打包。
他们可以利于node中的多进程,同时利用多个cpu进行打包,具体需要开几个cpu进行打包速度是最快的,对其他应用影响是最小的,要根据项目实际情况,多做几次尝试来做一个权衡。
8. 合理使用 sourceMap(查看详解)
9. 结合stats.json文件分析打包结果
10. 开发环境内存编译
webpack-dev-server做打包的时候不会生成dist目录,而是会把打包生成的文件放到内存里,那么内存的读取,肯定要比硬盘的读取要快的多,所以采用这种手段,也会让我们在开发的过程中webpack打包的性能得到很大的提升。
另外,提升webpack打包速度的方式还有很多,比如:一些loader会提供一些参数,帮助我们去提升它的打包速度。
知识补充
我们对webpack.dll.js文件里的entry再做一个变更:(对打包的dll文件做拆分)
entry: {
vendors: ['lodash'],
react: ['react', 'react-dom'] // 让react、react-dom库的代码打包到文件名为react的文件里
}
在webpack公共配置文件中多配置一份插件:
单独打包第三方库代码:(执行打包)
npm run build:dll
此时的dll目录如下:
再次执行打包:
npm run dev
页面显示正常,react、vendors也可以正常访问。
如果在大型项目中,对dll文件做拆分比较多的话,这样会导致在webpack公共的配置文件里,重复实例化多个相同的插件,下边分享个更好的方法:
通过node来分析dll目录下究竟有几个dll文件和manifest文件,然后动态的往plugins里添加AddAssetHtmlWebpackPlugin和DllReferencePlugin。
const path = require('path'); // 引入node的path模块(loader模块)
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
// 创建一个插件数组
const plugins = [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin()
]
// 使用fs读取dll目录下的文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
// 对获取到的所有文件进行循环
files.forEach(file => {
if(/.*\.dll.js/) { // 如果是以.dll.js结尾的文件,就以走AddAssetHtmlWebpackPlugin插件
plugins.push(
new AddAssetHtmlWebpackPlugin({ // 为页面引入打包好的第三方库代码文件
filepath: path.resolve(__dirname, '../dll', file)
})
)
}
if(/.*\.manifest.json/) { // 如果是以.manifest.json结尾的文件,就走DllReferencePlugin插件
plugins.push(
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
})
)
}
})
module.exports = {
entry: {
main: './src/index.js'
},
resolve: { // 新增配置项(如果想在模块里引入js文件,直接可省略后缀的话)
extensions: ['.js', '.jsx']
},
module: {
rules: [{
test: /\.jsx?$/, // 这里代表既匹配js文件,也匹配jsx文件
include: path.resolve(__dirname, '../src'), // 只对src目录下的js文件做打包转译工作
// exclude: /node_modules/, // 如果你的js文件在node_modules里边,就不使用babel-loader了,因为它里边的代码都是些第三方代码,已经做好了转译的工作。
use: [{
loader: "babel-loader"
}]
}]
},
plugins, // 插件配置项
optimization: {
splitChunks: { // 有默认配置项
chunks: "all" // 不管是同步还是异步,都进行代码分割
}
},
output: {
filename: '[name].js', // 打包之后的输出文件
path: path.resolve(__dirname, '../dist')
}
}
执行打包:
npm run dev
打开控制台我们发现,index.html页面引入了我们打包过后的第三方库文件。
此时,再对打包的dll文件做拆分,就比较方便了,每次做拆分就不必一次次的添加插件的实例了。