前端阅读 395月28日 03:28
Webpack 有哪些优化手段?
Webpack 有哪些优化手段?从构建速度和产物体积两个方向回答,面试中最常考察的优化点如下:构建速度优化1. 持久化缓存// webpack.config.jsmodule.exports = { cache: { type: 'filesystem', // Webpack 5 支持 buildDependencies: { config: [__filename] } }}首次构建后缓存写入 node_modules/.cache/webpack,二次构建可减少 60%-80% 时间。修改配置文件会自动失效。2. 缩小 Loader 处理范围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:并行压缩 JSWebpack 5 原生支持 parallel 配置4. 优化模块解析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 适用)// webpack.dll.config.jsnew webpack.DllPlugin({ name: '[name]', path: path.join(__dirname, 'dll', '[name]-manifest.json')})将 React、Vue 等不变库提前编译成 DLL,开发构建跳过。Webpack 5 的持久化缓存已基本替代此方案。产物体积优化1. 代码分割(Code Splitting)splitChunks — 提取公共依赖:module.exports = { optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } }}将 node_modules 中的库打包到独立 chunk,利用浏览器缓存。动态 import() — 按需加载:const Home = React.lazy(() => import('./Home'));const About = React.lazy(() => import('./About'));路由级懒加载,首屏只加载当前路由代码。 splitChunks 是配置层面的自动提取,动态 import() 是代码层面的手动分割,两者配合使用。2. Tree Shakingmodule.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(作用域提升)module.exports = { optimization: { concatenateModules: true, // 生产模式默认开启 }}将模块合并到同一个闭包中,减少函数声明和闭包数量,体积可减小 5%-10%。4. 压缩JS:terser-webpack-plugin(Webpack 5 生产模式默认启用)CSS:css-minimizer-webpack-pluginconst CssMinimizerPlugin = require('css-minimizer-webpack-plugin');module.exports = { optimization: { minimizer: [ new CssMinimizerPlugin(), ], }}5. externals 排除大型库module.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM' }}将 React、Lodash 等通过 CDN 引入,不打包进 bundle,显著减小产物体积。6. 图片与资源优化小于 8KB 的图片转 base64 内联(Webpack 5 的 asset/inline)大图使用 image-webpack-loader 压缩或转 WebP{ test: /\.(png|jpg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 } // 8KB 以下内联 }}7. IgnorePlugin 排除无用模块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(消费远程模块),实现应用间的模块级复用。