Webpack 构建优化:包体积优化

1. 面试题

Q: 如果打包出来的 JS 体积过大,你会从哪些方面进行优化?

2. 核心解答

包体积优化是为了:更快加载。核心手段是 压缩拆分

(1) 代码分割 (Code Splitting)

把庞大的 Bundle 切分成多个小的 Chunk,按需加载或缓存。

  • SplitChunksPlugin (Webpack 4+): 将第三方库 (vendors) 和公共代码 (commons) 从 main.js 中提取出来。
    optimization: {
        splitChunks: {
            chunks: 'all', // 同步和异步都拆
            cacheGroups: {
                vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }
            }
        }
    }
  • Dynamic Import: 使用 import() 语法实现路由懒加载。Webpack 会自动将该模块打包成单独的文件,仅在路由激活时才请求。

(2) 摇树优化

  • Dead Code Elimination: 移除未使用的代码。
  • Requirement: 必须使用 ESM (import/export)。CommonJS 不支持 Tree Shaking。
  • SideEffects: 在 package.json 中声明 sideEffects: false,告诉 Webpack 该库没有任何副作用,即使被引用了但没使用导出,也可以安全地删掉整个文件。
    • CSS 问题: 需要把 css 后缀加入 sideEffects 数组,防止 CSS 被误删。

(3) 文件压缩

  • JS: TerserPlugin (默认开启),移除空格、注释、缩短变量名。
  • CSS: CssMinimizerPlugin (需手动配置),压缩 CSS。
  • Gzip: 服务端开启 gzip,能在 HTTP 传输层面减少 70% 的体积。

(4) 作用域提升

  • ModuleConcatenationPlugin (Webpack 3+,生产模式默认开启)。
  • 原理:将多个模块的代码放在同一个函数作用域中,减少了函数声明的开销和代码体积。
  • 条件:必须使用 ESM。

(5) 移除冗余资源

  • ContextReplacementPlugin: (经典案例 Moment.js)。Moment 默认包含几十种语言包,只保留 zh-cn 即可减少几百 KB。
  • IgnorePlugin: 忽略某些模块。

3. 面试加分项

Q: Tree Shaking 为什么必须是 ESM?

因为 ESM 是静态结构 (Static Structure),编译时就能确定依赖关系。而 CommonJS 是动态的 (require(condition ? 'a' : 'b')),无法在不运行代码的情况下确定到底引用了什么。

4. 总结

  • Split: Vendor Chunk, Async Chunk.
  • Remove: Tree Shaking, SideEffects: false.
  • Mini: Terser, CssMinimizer.
  • Hoist: Scope Hoisting.