webpack 性能优化
构建优化手册: https://tsejx.github.io/webpack-guidebook/best-practice/optimization/collection
提升 Webpack 构建速度是优化前端开发体验和提高开发效率的重要方面。
以下是一些常见的提升 Webpack 构建速度的技巧:
thread-loader
在Webpack 5中,thread-loader可以用来提高构建速度,特别是在处理大型项目或需要进行复杂处理的任务(如Babel转换)时。thread-loader工作原理是将部分loader处理工作分配到一个单独的worker线程池中,从而实现并行处理。这样做可以减少主线程的负担,提高构建效率,尤其是在多核CPU系统上。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve("src"),
use: [
'thread-loader', // 添加 thread-loader
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
]
}
]
}
}
- workers: 指定worker线程的数量。默认是os.cpus().length - 1。
- workerParallelJobs: 每个worker线程可以处理的并行作业数。默认是20。
- poolRespawn: 如果设置为false,worker线程在退出时不会被重启。这对长期缓存有利。
注意事项
- 启动时间:thread-loader会为每个worker线程创建一个新的Node.js环境,这会有一定的启动开销。因此,它主要适用于大型项目或长时间运行的构建任务。
- 资源使用:增加线程数将增加内存消耗,因此应根据系统资源适当配置worker数量。
- 兼容性:确保使用的loader与thread-loader兼容,因为并不是所有的loader都能在worker线程中正确执行。
提升 webpack 构建速度方法
- 使用缓存和持久化构建结果:利用缓存和持久化构建结果,避免重复构建相同的模块。可以使用 Webpack 内置的缓存功能或者借助插件如 cache-loader、hard-source-webpack-plugin 等来实现。
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
cache: true,
// 使用 cache-loader 插件缓存 loader 的执行结果
module: {
rules: [
{
test: /\.js$/,
use: 'cache-loader',
include: path.resolve(__dirname, 'src')
}
]
}
};
- 使用多进程和多实例构建:利用 Webpack 的并行构建功能,通过 thread-loader、happypack 等插件实现多进程构建,加速构建过程。这可以利用多核 CPU 的优势,同时处理多个任务。
// webpack.config.js
const path = require('path');
const HappyPack = require('happypack');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 使用 happypack 插件实现多进程构建
plugins: [
new HappyPack({
loaders: ['babel-loader']
})
],
module: {
rules: [
{
test: /\.js$/,
// 使用 happypack 插件处理 JavaScript 文件
use: 'happypack/loader',
include: path.resolve(__dirname, 'src')
}
]
}
};
- 减小文件搜索范围:优化 Webpack 的 resolve.modules、resolve.extensions、resolve.mainFields 等配置,减小文件搜索范围,降低查找文件的时间。
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
// 只在指定的目录中查找模块
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
// 只解析指定的扩展名
extensions: ['.js', '.json'],
// 指定优先使用的文件名
mainFiles: ['index', 'main']
}
};
- 合理使用 alias:通过 alias 配置,指定模块的别名,避免过深的模块查找路径,提高模块的解析速度。
// webpack.config.js
const path = require('path');
module.exports = {
// 其他配置...
resolve: {
alias: {
// 配置别名,将 'src/components' 映射为 'src/app/components'
'@': path.resolve(__dirname, 'src/app'),
// 配置别名,将 'react' 映射为 'preact/compat'
'react': 'preact/compat',
'react-dom': 'preact/compat'
}
}
};
- 优化 loader 和插件:精简和优化 loader 和插件的配置,确保只加载需要的 loader 和插件,避免加载过多不必要的模块。
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
// 使用 babel-loader,并开启缓存
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
},
include: path.resolve(__dirname, 'src')
}
]
}
};
- 开启生产模式优化:在生产环境中,开启 Webpack 的压缩和优化功能,如压缩代码、移除未使用的代码(Tree Shaking)、作用域提升等,减小输出文件的体积。
// webpack.config.js
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
minimizer: [
// 使用 TerserPlugin 压缩 JavaScript 代码
new TerserPlugin({
parallel: true // 开启并行压缩
})
]
}
};
- externals 配置:用于告诉 Webpack 在打包过程中忽略某些模块的打包,将这些模块视为外部依赖,由运行时环境提供。
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
externals: {
// 将 lodash 模块排除在打包过程之外,并在运行时从全局变量 _ 中获取
lodash: '_'
}
};
- 合理使用代码拆分和按需加载:通过代码拆分和按需加载,将应用程序拆分成多个较小的 Chunk,在需要时动态加载,减少初始加载时间和减小输出文件的体积。
- 开启并行压缩:在 Webpack 的配置中开启并行压缩,如使用 parallel-uglify-plugin 插件,可以同时压缩多个文件,提高压缩速度。
- 使用动态链接库(DLL):将第三方库打包成动态链接库,在开发过程中不需要重新构建,提高开发效率。(动态链接库包含了这些库的代码,并且会生成一个 manifest 文件,记录了动态链接库中的模块信息和对应的模块标识符)
- 提升构建速度:动态链接库(DLL)可以将一些不常变化的第三方库单独打包成一个动态链接库,然后在开发过程中,通过 DllReferencePlugin 引入该动态链接库,避免在每次构建过程中重复打包这些第三方库,从而提升构建速度。
- 减小打包体积:将一些不常变化的第三方库打包成动态链接库后,可以减小打包出的主要代码文件的体积,加快应用程序的加载速度。
- 缓存利用:由于动态链接库不经常改变,因此可以使用浏览器的缓存机制,从而加快应用程序的加载速度。
// webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
vendor: ['react', 'react-dom', 'lodash']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dll'),
library: '[name]_dll' // 导出的变量名
},
plugins: [
new webpack.DllPlugin({
name: '[name]_dll', // 和 output.library 一致
path: path.resolve(__dirname, 'dll/[name].manifest.json')
})
]
};
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
// 使用 DllReferencePlugin 引入动态链接库
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dll/vendor.manifest.json')
})
]
};