Files
zhuangang/topics/frontend/webpack/01-build-optimization.md
T

4.6 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
id title type domain stack tags level prerequisites est_hours related week order
frontend.webpack.build-optimization webpack 5:loader 与 plugin、splitChunks、tree shaking、缓存、模块联邦 lesson frontend
webpack
webpack
loader
plugin
splitChunks
tree shaking
contenthash
持久化缓存
Module Federation
intermediate
2.5
frontend.vite.dev-build-config
frontend.webpack.scenario.build-speed
frontend.webpack.interview
7
1

webpack 5:loader 与 plugin、splitChunks、tree shaking、缓存、模块联邦

基于 webpack 5.112 + webpack-cli 7 编写并实际构建验证。很多公司的存量项目仍在用 webpack,面试也常问原理。以官方文档为准:https://webpack.js.org/concepts/ 。

解释说明

核心概念

概念 一句话
entry 从哪里开始分析依赖
output 产物放在哪里、叫什么名字
loader 把非 JS 文件(或需要转换的 JS)转换成 JS 模块;从右到左(从下到上)链式执行
plugin 在编译生命周期的钩子上做事(生成 HTML、压缩、提取 CSS、分析体积),基于 tapable 事件机制
chunk 打包过程中的代码块;入口、动态 import()、splitChunks 都会产生 chunk
mode development / production,决定默认优化项

构建流程(面试常问)

初始化参数 → 创建 Compiler → 从 entry 开始,对每个模块调用匹配的 loader 转换 → 解析出依赖,递归构建模块依赖图 → 根据入口和拆包规则组装 chunk → 生成代码写入 output。过程中在各个阶段触发钩子,plugin 在钩子上执行。

优化手段

  • tree shaking:依赖 ES 模块的静态结构,usedExports 标记未使用的导出,压缩时删除;package.json 的 sideEffects 声明哪些文件有副作用(如 CSS),其余可以整体删除。
  • splitChunks:提取公共代码和第三方库;runtimeChunk: "single" 把运行时单独拆出。
  • contenthash:文件名随内容变化,配合 CDN 长期缓存。
  • 持久化缓存:cache: { type: "filesystem" },第二次构建明显变快。
  • 构建提速:缩小 loader 的处理范围(include / exclude)、用 swc / esbuild 类 loader 代替 babel、多进程、减少不必要的 source map。
  • Module Federation(模块联邦):多个独立构建的应用在运行时共享模块,用于微前端。

案例

一个主站和后台两个入口共享工具函数:通过 splitChunks 提取出 shared,运行时拆出 runtime,报表模块用动态 import() 按需加载。

代码示例

完整项目:examples/webpack-config,npm install && npm run build 输出:

[size] runtime.981552ae.js                  2.74 KiB
[size] main.38cd14b7.js                     0.33 KiB
[size] 531.30ecc347.js                      0.21 KiB     ← 动态 import 的 report.js
[size] admin.e2a620fd.js                    0.20 KiB
[size] shared.5f4a3ab4.js                   0.18 KiB     ← 两个入口共用的 utils.js
Entrypoint main 3.25 KiB = runtime + shared + main
Entrypoint admin 3.12 KiB = runtime + shared + admin

已验证:utils.js 中未被使用的 unusedHelper 在产物中搜索不到(tree shaking 生效)。

自定义 loader(loaders/md-loader.cjs):

module.exports = function mdLoader(source) {
  const { trim = false } = this.getOptions();
  const text = trim ? source.trim() : source;
  return `export default ${JSON.stringify(text)};`;
};

自定义 plugin(webpack.config.js):

class SizeReportPlugin {
  apply(compiler) {
    compiler.hooks.done.tap("SizeReportPlugin", (stats) => {
      const assets = stats.toJson({ all: false, assets: true }).assets ?? [];
      for (const a of assets) console.log(`[size] ${a.name} ${(a.size / 1024).toFixed(2)} KiB`);
    });
  }
}

易错点

  • 混淆 loader 和 plugin 的职责。
  • loader 的执行顺序写反(use: ["style-loader", "css-loader"] 中 css-loader 先执行)。
  • 用了 CommonJS(require / module.exports)导致 tree shaking 失效;sideEffects: false 配置错误导致 CSS 被删掉。
  • 用 [hash] 而不是 [contenthash],每次构建所有文件名都变,缓存失效。
  • 生产环境开启了暴露源码的 source map 并公开访问。

面试常问

  • loader 和 plugin 的区别?写过吗?
  • webpack 的构建流程?
  • tree shaking 的原理和失效原因?
  • 怎么优化构建速度和产物体积?