phase 5 part 2: vite 8, webpack 5, fe-testing-perf topics with verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:29:27 +08:00
1 parent 7b5cae7d24
commit 9eeb10aede
38 files changed
+1823 -37

No files matched your search

@@ -0,0 +1,102 @@
---
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 的原理和失效原因?
- 怎么优化构建速度和产物体积?