5月28日 03:28

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 处理范围

js
module.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. 优化模块解析

js
module.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 — 提取公共依赖:

js
module.exports = { optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } } }

node_modules 中的库打包到独立 chunk,利用浏览器缓存。

动态 import() — 按需加载:

js
const Home = React.lazy(() => import('./Home')); const About = React.lazy(() => import('./About'));

路由级懒加载,首屏只加载当前路由代码。

splitChunks 是配置层面的自动提取,动态 import() 是代码层面的手动分割,两者配合使用。

2. Tree Shaking

js
module.exports = { mode: 'production', // 自动开启 optimization: { usedExports: true, minimize: true, } }

前提条件:

  • 必须使用 ESM(import/export),不能用 CommonJS(require
  • babel-loader 配置 "modules": false 防止转译为 CJS
  • package.json 标记 "sideEffects": false 告知安全移除未使用导出

为什么需要 ESM? ESM 的 import/export 是静态声明,编译时可确定依赖关系;CommonJS 的 require 是运行时调用,可以在 if 中使用,打包工具无法静态分析。

3. Scope Hoisting(作用域提升)

js
module.exports = { optimization: { concatenateModules: true, // 生产模式默认开启 } }

将模块合并到同一个闭包中,减少函数声明和闭包数量,体积可减小 5%-10%。

4. 压缩

  • JS:terser-webpack-plugin(Webpack 5 生产模式默认启用)
  • CSS:css-minimizer-webpack-plugin
js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { optimization: { minimizer: [ new CssMinimizerPlugin(), ], } }

5. externals 排除大型库

js
module.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 排除无用模块

js
new 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(消费远程模块),实现应用间的模块级复用。

标签:JavaScript前端Webpack