4.6 KiB
4.6 KiB
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 |
|
|
intermediate | 2.5 |
|
|
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 的原理和失效原因?
- 怎么优化构建速度和产物体积?