Webpack 优化技巧
javascript module: { rules: [ { test: /.js$/, use: 'babel-loader', include: [path.resolve(__dirname, 'src')], // 只处理 src 下的文件 exclude: /node_modules/, }, ]; }
- **优化 `resolve.modules` 与 `resolve.alias`**
指定 Webpack 解析模块时应当检索的目录,并利用别名避免多层相对路径的查找。
```javascript
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'], // 优先从 src 查找
alias: {
'@': path.resolve(__dirname, 'src'),
utils: path.resolve(__dirname, 'src/utils/'),
},
},
-
减少后缀尝试
通过
extensions明确模块文件后缀,并尽量将高频后缀放在前面。resolve: { extensions: ['.js', '.jsx', '.json']; // 少用 .css 等,避免额外匹配 }
2. 引入缓存机制
缓存能让二次构建直接复用上次结果,大幅缩短冷启动后的编译时长。
-
开启 Babel 缓存
babel-loader自带cacheDirectory选项,会缓存转译结果到node_modules/.cache/babel-loader。{ test: /\.js$/, use: { loader: 'babel-loader', options: { cacheDirectory: true, }, }, } -
Webpack 内置持久化缓存
Webpack 5 原生支持磁盘缓存,通过简单的配置就能实现模块和 chunk 的持久化。
module.exports = { cache: { type: 'filesystem', // 将缓存存储到 node_modules/.cache/webpack }, };开发模式下配合
cache属性,二次启动速度极快。
3. 多进程/多实例构建
利用 CPU 多核并行处理,把耗时的 Loader 工作分配给多个子进程。
-
使用 thread-loader
把
thread-loader放在其他 loader 之前,它会将该 loader 的工作分配到 worker 池中。rules: [ { test: /\.js$/, use: ['thread-loader', 'babel-loader'], include: path.resolve('src'), }, ];注意:启动 worker 有开销,仅当项目文件量大时才启用,切勿滥用。
-
并行压缩插件
terser-webpack-plugin默认开启多进程压缩,你也可以通过parallel显式控制。const TerserPlugin = require('terser-webpack-plugin'); optimization: { minimizer: [ new TerserPlugin({ parallel: true, // 默认值为 cpu 核心数 -1 }), ], }
4. 使用 DLL 预编译(Webpack 4 及以下)
尽管 Webpack 5 内置更好的长期缓存策略,但传统项目仍考虑用 DLL 提前打包不动如山的第三方库。
-
第一步:创建
webpack.dll.config.js将 Vue、React、lodash 等稳定依赖预先打包。
const webpack = require('webpack'); module.exports = { entry: { vendor: ['react', 'react-dom', 'lodash'], }, output: { path: path.resolve(__dirname, 'dll'), filename: '[name].dll.js', library: '[name]_library', }, plugins: [ new webpack.DllPlugin({ name: '[name]_library', path: path.resolve(__dirname, 'dll/manifest.json'), }), ], }; -
第二步:主配置中引用 DLL
使用
DllReferencePlugin映射依赖,并借助add-asset-html-webpack-plugin自动注入 script 标签。plugins: [ new webpack.DllReferencePlugin({ manifest: require('./dll/manifest.json'), }), ];该方法在 Webpack 5 中已不推荐,可改用
HardSourceWebpackPlugin或直接使用内置缓存。
5. 选择合适的 Source Map 模式
开发时不同 devtool 设置对编译速度影响巨大。追求速度时选 eval-cheap-module-source-map,要精确调试时选 eval-source-map。
devtool: 'eval-cheap-module-source-map'; // 构建快,源码映射准确
生产环境若需定位问题,推荐 source-map (会生成独立 .map 文件) 或 hidden-source-map。
二、输出质量优化
构建产物的体积直接决定了应用加载速度,优化输出是性能优化的重中之重。
1. Tree Shaking 与副作用处理
Tree Shaking 依赖于 ES Module 的静态结构,用于消除未引用的代码。
-
确保 babel 保留 ES module 语法
@babel/preset-env需要设置modules: false,否则会将 import 转成 CommonJS 导致 Tree Shaking 失效。{ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: [['@babel/preset-env', { modules: false }]], }, }, } -
在
package.json中标记副作用通过
sideEffects字段告知 Webpack 哪些文件可以安全删除未引用代码。{ "sideEffects": ["*.css", "*.less"] // 防止删除包含样式导入的模块 }设置为
false表示所有文件均可被 Tree Shaking。
2. 代码分割(Code Splitting)
Webpack 内置三种代码分割方式:入口起点分割、动态导入分割、SplitChunksPlugin 配置分割。
-
动态导入(按需加载)
使用
import()语法,Webpack 会自动将模块拆分为独立 chunk。button.addEventListener('click', () => { import('./moduleA').then((module) => { module.default(); }); }); -
配置
splitChunks提取公共模块将第三方库、公共业务模块提取为单独的缓存块。
optimization: { splitChunks: { chunks: 'all', // 处理所有类型的 chunks cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 10, reuseExistingChunk: true, }, common: { minChunks: 2, name: 'common', priority: 5, reuseExistingChunk: true, }, }, }, }通过智能分割,浏览器能够并行加载,并利用缓存减少重复请求。
3. 压缩与优化
-
CSS 压缩
使用
css-minimizer-webpack-plugin在 Webpack 5 中压缩 CSS,它基于cssnano。const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); optimization: { minimizer: [new CssMinimizerPlugin()], }注意:需要在
mini-css-extract-plugin之后运行,并确保生产环境使用。 -
JavaScript 压缩
terser-webpack-plugin自带 dead code elimination,可移除无用代码、缩短变量名。 -
图片与字体资源优化
利用
image-webpack-loader在打包时压缩图片,或asset-modules的parser.dataUrlCondition.maxSize将小资源转为 base64 内联。{ test: /\.(png|jpe?g|gif)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024, // 小于8KB的文件转为base64 }, }, use: [ { loader: 'image-webpack-loader', options: { mozjpeg: { quality: 70 } }, }, ], }
4. Scope Hoisting(作用域提升)
Webpack 默认会将每个模块用函数包裹,Scope Hoisting 会分析模块依赖,尽量将所有模块合并到一个函数中,从而减少闭包数量、提升执行速度。
在 Webpack 4 中需引入 ModuleConcatenationPlugin,Webpack 5 生产模式下自动启用。
// Webpack 4 手动启用
plugins: [new webpack.optimize.ModuleConcatenationPlugin()];
前提条件是需要使用 ES Module 语法。
5. 使用长效缓存策略
要让浏览器缓存生效,需要保证文件内容变化时文件名也跟着变化(哈希),否则用户拿到的仍是旧文件。
-
配置 output 文件名哈希
output: { filename: '[name].[contenthash:8].js', chunkFilename: '[name].[contenthash:8].chunk.js', }contenthash根据文件内容生成,只有内容改变时 hash 才变。 -
提取稳定模板文件
利用
HtmlWebpackPlugin生成 HTML 并自动注入变化后的 script 标签。将不常变的 vendor 和 runtime 分离出来,保证业务代码变更时,三方库的缓存不被破坏。optimization: { runtimeChunk: 'single', // 单独提取 webpack 运行时 splitChunks: { cacheGroups: { vendor: { test: /node_modules/, name: 'vendor', chunks: 'all', }, }, }, }
三、开发体验优化
除了速度与体积,开发过程中的反馈速度同样重要。
1. 模块热替换(HMR)的正确姿势
HMR 允许在运行时更新模块,无需刷新页面,保持应用状态。
-
启用 HMR 基础配置
devServer: { hot: true, open: true, }, // 并引入 webpack 内置插件(Webpack 5 自动包含) // plugins: [new webpack.HotModuleReplacementPlugin()] -
在代码中处理模块更新
某些模块(如样式)会自动热更新,JS 逻辑则需要手动处理,避免状态丢失。
if (module.hot) { module.hot.accept('./module', () => { // 重新执行渲染逻辑 }); }
2. 选择合适的 Devtool
开发时建议使用 eval-cheap-module-source-map,它在构建速度和源码映射精度之间取得了平衡。
3. 减少不必要的插件与 Loader
仅在生产环境执行压缩、代码分析等插件。利用 Webpack 的 mode 自动开启优化,避免开发模式下应用压缩导致速度变慢。
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
optimization: {
minimize: isProduction,
minimizer: isProduction ? [new TerserPlugin()] : [],
},
};
};
4. 使用更快的 Loader
- 用
esbuild-loader替代babel-loader可大幅提速(注意兼容性)。 - CSS 处理链尽量精简,例如用
sass替换node-sass。
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader',
options: { target: 'es2015' },
},
];
四、其它实用技巧
-
分析打包结果
借助webpack-bundle-analyzer可视化查看各模块体积,有针对性地优化。const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; plugins: [new BundleAnalyzerPlugin()]; -
懒编译(Lazy Compilation)
Webpack 5 实验特性,动态导入的模块仅在被使用时才编译,极大加速大型项目开发启动速度。experiments: { lazyCompilation: true, } -
external 外部化
将体积大的库如 jQuery、React 从 CDN 引入,配置externals,既减少打包体积又加快构建。externals: { react: 'React', 'react-dom': 'ReactDOM', } -
使用 IgnorePlugin 忽略无用文件
例如 moment.js 的语言包,按需引入:plugins: [new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ })];