module、chunk、bundle 经常都被叫成“包”,但排查构建时,它们指向的层次不同。一个依赖进了哪个 chunk,和最终下载了哪个文件,也不是完全相同的问题。
用一次动态 import,把源码和输出对起来看会直观一些。
在空目录运行 npm init -y 和 npm install —save-dev —save-exact webpack@5.99.9。下面用 Node API 构建,不依赖全局 webpack 命令。
创建 math.js:
export const used = (a, b) => a + b;
创建 lazy.js:
export default "lazy loaded";
创建 entry.js:
import { used } from "./math.js";
globalThis.bundleResult = used(2, 3);
import("./lazy.js").then((module) => {
globalThis.lazyResult = module.default;
});
创建 build.cjs:
const webpack = require("webpack");
const path = require("path");
webpack(
{
mode: "production",
entry: path.resolve("entry.js"),
output: {
path: path.resolve("dist"),
filename: "main.js",
chunkFilename: "[id].js",
},
devtool: false,
},
(error, stats) => {
if (error || stats.hasErrors()) {
console.error(error || stats.toString("errors-only"));
process.exitCode = 1;
return;
}
console.log(
JSON.stringify(
stats.toJson({
all: false,
assets: true,
chunks: true,
modules: true,
usedExports: true,
}),
null,
2,
),
);
},
);
node build.cjs 会输出 stats。dist 中有 main.js 和另一个异步 JS 文件,在这份配置里,动态模块形成了额外的 chunk。
module 是构建处理的单元
一般我们将一个源文件,认为是一个module。
入口、math.js 和 lazy.js 参与模块图。Loader 处理后的结果、生成的虚拟模块也可能成为 module,所以不能把 module 永远理解成一个物理源文件。
入口静态引用 math.js,动态引用 lazy.js。构建工具基于这些依赖决定如何组织加载。
chunk 组织模块,asset 是输出资源
入口对应初始加载部分,动态 import 对应后续加载部分。stats 中的 chunks 可以看到是否 initial,以及关联的 files。Webpack 的 runtime 还负责在浏览器里加载和执行异步部分。
asset 是最终输出的资源,包括 JS、CSS、图片和 source map。一个 chunk 可能关联多个文件,不是所有 asset 都能直接叫作应用 JS bundle。bundle 更多是讨论打包后代码文件时使用的说法。
如果开启模块拼接,stats 的 modules 还可能嵌套。只数顶层 modules,不一定得到全部源码模块的数量。
文件拆出来以后,怎么检查加载
创建 index.html,放在项目根目录:
<!doctype html><meta charset="utf-8" /><title>动态加载</title>
<script src="./dist/main.js"></script>
用编辑器的本地 HTTP 服务打开,观察 Network。这个入口立刻调用 import,所以异步文件也会立即开始请求;“拆成异步 chunk”并不等于“等用户点击才下载”。在控制台看 bundleResult 为 5,lazyResult 为 lazy loaded。
真正按交互延后加载,需要把 import 放到对应的事件或路由边界。是否提取 runtime、共享 chunk 和缓存策略会进一步改变产物,还是要看当前配置的 stats 和请求。规则参考 Webpack 代码分割文档。