Webpack 有哪些优化手段?
Webpack 有哪些优化手段?
从构建速度和产物体积两个方向回答,面试中最常考察的优化点如下:
构建速度优化
1. 持久化缓存
js// webpack.config.js module.exports = { cache: { type: 'filesystem', // Webpack 5 支持 buildDependencies: { config: [__filename] } } }
首次构建后缓存写入 node_modules/.cache/webpack,二次构建可减少 60%-80% 时间。修改配置文件会自动失效。
2. 缩小 Loader 处理范围
jsmodule.exports = { module: { rules: [{ test: /\.js$/, include: path.resolve(__dirname, 'src'), // 只处理 src 目录 exclude: /node_modules/, use: 'babel-loader' }] } }
不配置 include 时,Babel 会遍历 node_modules 中所有 JS 文件,构建时间翻倍。
3. 多线程编译
thread-loader:将耗时的 loader 放到 worker 池中并行执行terser-webpack-plugin开启parallel: true:并行压缩 JS- Webpack 5 原生支持
parallel配置
4. 优化模块解析
jsmodule.exports = { resolve: { extensions: ['.ts', '.tsx', '.js'], // 按使用频率排列,减少尝试次数 alias: { '@': path.resolve(__dirname, 'src') }, // 别名减少路径查找 }, module: { noParse: /lodash|jquery/, // 跳过已打包库的解析 } }
noParse 告诉 Webpack 这些库没有 import/require,不需要解析依赖关系。
5. 使用更快的编译器
swc-loader替代babel-loader(Rust 实现,速度快 20 倍以上)esbuild-loader替代terser-webpack-plugin(Go 实现)
6. DLL 预编译(Webpack 4 适用)
js// webpack.dll.config.js new webpack.DllPlugin({ name: '[name]', path: path.join(__dirname, 'dll', '[name]-manifest.json') })
将 React、Vue 等不变库提前编译成 DLL,开发构建跳过。Webpack 5 的持久化缓存已基本替代此方案。
产物体积优化
1. 代码分割(Code Splitting)
splitChunks — 提取公共依赖:
jsmodule.exports = { optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } } }
将 node_modules 中的库打包到独立 chunk,利用浏览器缓存。
动态 import() — 按需加载:
jsconst Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About'));
路由级懒加载,首屏只加载当前路由代码。
splitChunks 是配置层面的自动提取,动态 import() 是代码层面的手动分割,两者配合使用。
2. Tree Shaking
jsmodule.exports = { mode: 'production', // 自动开启 optimization: { usedExports: true, minimize: true, } }
前提条件:
- 必须使用 ESM(
import/export),不能用 CommonJS(require) babel-loader配置"modules": false防止转译为 CJSpackage.json标记"sideEffects": false告知安全移除未使用导出
为什么需要 ESM? ESM 的 import/export 是静态声明,编译时可确定依赖关系;CommonJS 的 require 是运行时调用,可以在 if 中使用,打包工具无法静态分析。
3. Scope Hoisting(作用域提升)
jsmodule.exports = { optimization: { concatenateModules: true, // 生产模式默认开启 } }
将模块合并到同一个闭包中,减少函数声明和闭包数量,体积可减小 5%-10%。
4. 压缩
- JS:
terser-webpack-plugin(Webpack 5 生产模式默认启用) - CSS:
css-minimizer-webpack-plugin
jsconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { minimizer: [ new CssMinimizerPlugin(), ], } }
5. externals 排除大型库
jsmodule.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM' } }
将 React、Lodash 等通过 CDN 引入,不打包进 bundle,显著减小产物体积。
6. 图片与资源优化
- 小于 8KB 的图片转 base64 内联(Webpack 5 的
asset/inline) - 大图使用
image-webpack-loader压缩或转 WebP
js{ test: /\.(png|jpg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 } // 8KB 以下内联 } }
7. IgnorePlugin 排除无用模块
jsnew webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ })
忽略 moment.js 的 locale 文件,体积减少约 200KB。
构建分析工具
| 工具 | 用途 |
|---|---|
webpack-bundle-analyzer | 可视化分析产物体积,找出大依赖 |
speed-measure-webpack-plugin | 统计各 loader/plugin 耗时 |
webpack --profile --json > stats.json | 导出构建统计数据 |
Webpack 5 新特性补充
- Module Federation:跨应用共享模块,实现微前端,避免重复打包公共依赖
- 持久化缓存:替代 DLL 方案,配置更简单
- Asset Modules:内置资源模块,替代
url-loader/file-loader - 更好的 Tree Shaking:支持嵌套的 Tree Shaking 和内部模块的 Tree Shaking
追问
splitChunks 和动态 import 有什么区别?
splitChunks 是 Webpack 配置层面的自动优化,根据策略提取公共依赖(如 node_modules 中的库),开发者无需手动干预。动态 import() 是代码层面的手动分割,由开发者决定哪些模块按需加载。两者互补:splitChunks 处理共享依赖,动态 import 处理路由级懒加载。
Tree-Shaking 为什么需要 ESM?
ESModule 的 import/export 是静态声明,在编译阶段就能确定模块的依赖关系和哪些导出被使用。CommonJS 的 require 是运行时调用,可以写在 if 语句、循环或函数中,打包工具无法在编译时判断哪些代码会被执行,因此无法安全移除未使用的代码。此外,需确保 Babel 不将 ESM 转译为 CJS(配置 "modules": false),并在 package.json 中声明 "sideEffects": false。
webpack 的 cache 缓存了什么?为什么能加快构建?
缓存了每个模块的 loader 处理结果和编译产物。首次构建后写入磁盘(node_modules/.cache/webpack),二次构建时 Webpack 检查文件时间戳和内容 hash,只重新编译变化的模块及其依赖链上的模块,未变化的模块直接使用缓存。典型场景:修改一个组件文件,只重新编译该文件和引用它的文件,其余数千模块跳过,构建时间从 30s 降到 5s 以内。Webpack 5 的 filesystem 缓存相比 Webpack 4 的 memory 缓存,重启开发服务器后缓存不丢失。
Module Federation 解决了什么问题?
多个独立构建的应用之间共享模块,避免重复打包相同依赖。典型场景:微前端架构中,主应用和子应用都依赖 React,传统方案各自打包 React,用 Module Federation 可以在运行时共享一份 React,减少总体加载量。核心配置是 exposes(暴露模块)和 remotes(消费远程模块),实现应用间的模块级复用。