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

103 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: frontend.webpack.build-optimization
title: webpack 5:loader 与 plugin、splitChunks、tree shaking、缓存、模块联邦
type: lesson
domain: frontend
stack: [webpack]
tags: [webpack, loader, plugin, splitChunks, tree shaking, contenthash, 持久化缓存, Module Federation]
level: intermediate
prerequisites: []
est_hours: 2.5
related: [frontend.vite.dev-build-config, frontend.webpack.scenario.build-speed, frontend.webpack.interview]
week: [7]
order: 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](examples/webpack-config/),`npm install && npm run build` 输出:
```text
[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](examples/webpack-config/loaders/md-loader.cjs)):
```js
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](examples/webpack-config/webpack.config.js)):
```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 的原理和失效原因?
- 怎么优化构建速度和产物体积?