一、MainTemplate 与 ChunkTemplate 的作用
1.1 核心职责与源码定位
在 Webpack 中,MainTemplate(源码:lib/MainTemplate.js)和 ChunkTemplate(源码:lib/ChunkTemplate.js)是控制 Bundle 输出格式的代码生成器。
- MainTemplate 负责入口 Chunk(通常对应
entry配置)的代码生成,包含运行时代码(Runtime)和入口模块的初始化逻辑。 - ChunkTemplate 负责非入口 Chunk(如动态加载的异步 Chunk、通过
SplitChunksPlugin拆分的公共代码)的代码生成,通常只包含模块定义和与 Runtime 交互的逻辑。
1.2 源码结构与关键方法
MainTemplate 核心逻辑:
// lib/MainTemplate.js
class MainTemplate extends Template {
constructor(outputOptions) {
super(outputOptions);
// 注册核心 Hook
this.hooks = {
render: new SyncWaterfallHook(["source", "chunk", "hash", "moduleTemplate", "dependencyTemplates"]),
// ... 其他 Hook
};
}
// 渲染入口 Chunk 的核心方法
render(hash, chunk, moduleTemplate, dependencyTemplates) {
// 生成 Runtime 代码
const buf = this.hooks.bootstrap.call(
"",
chunk,
hash,
moduleTemplate,
dependencyTemplates
);
// 拼接模块代码
const source = this.hooks.render.call(
new OriginalSource(
buf + "\n" + chunk.modules.map(m => m.source()).join("\n"),
"webpack/bootstrap"
),
chunk,
hash,
moduleTemplate,
dependencyTemplates
);
return source;
}
}
ChunkTemplate 核心逻辑:
// lib/ChunkTemplate.js
class ChunkTemplate extends Template {
render(chunk, moduleTemplate, dependencyTemplates) {
// 直接渲染模块代码,不包含 Runtime
const source = new ConcatSource();
source.add(`// Chunk: ${chunk.id}\n`);
chunk.modules.forEach(module => {
source.add(module.source(moduleTemplate, dependencyTemplates));
});
return source;
}
}
1.3 实际场景对比
- 入口 Chunk(MainTemplate):生成
bundle.js,包含__webpack_require__、模块加载逻辑、启动代码。 - 异步 Chunk(ChunkTemplate):生成
0.bundle.js,仅包含模块定义和 JSONP 加载代码。
二、运行时代码(webpack_require)的生成逻辑
2.1 运行时代码组成
运行时代码负责模块的加载、缓存、解析依赖,核心函数 __webpack_require__ 的实现逻辑如下:
// 伪代码简化版 Runtime
function __webpack_require__(moduleId) {
// 1. 检查模块缓存
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// 2. 创建新模块并缓存
const module = (__webpack_module_cache__[moduleId] = { exports: {} });
// 3. 执行模块函数
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 4. 返回模块导出
return module.exports;
}
2.2 源码生成逻辑
运行时代码的生成分散在多个插件中,但核心逻辑由 RuntimePlugin(源码:lib/RuntimePlugin.js)注入到 MainTemplate。
关键步骤:
- 定义 RuntimeGlobals:在
lib/RuntimeGlobals.js中声明全局变量(如__webpack_require__)。 - 生成函数体:通过
RuntimeTemplate(源码:lib/RuntimeTemplate.js)生成__webpack_require__的代码字符串。 - 注入到 MainTemplate:在 Compilation 阶段,RuntimePlugin 向 MainTemplate 的 Hook 注册代码生成逻辑。
源码片段示例:
// lib/RuntimePlugin.js
compilation.runtimeTemplate.requireFn = "__webpack_require__";
// lib/MainTemplate.js
this.hooks.bootstrap.tap("MainTemplate", (source, chunk, hash) => {
// 生成 __webpack_require__ 定义
const requireCode = compilation.runtimeTemplate.requireFn;
return Template.asString([
source,
`var ${requireCode} = ${runtimeTemplate.basicFunction("moduleId", [
"// Check if module is cached",
`if (__webpack_module_cache__[moduleId]) {`,
Template.indent(`return __webpack_module_cache__[moduleId].exports;`),
"}",
"// Create a new module and cache it",
`var module = __webpack_module_cache__[moduleId] = {`,
Template.indent("exports: {}"),
"};",
"// Execute the module function",
`__webpack_modules__[moduleId](module, module.exports, ${requireCode});`,
"// Return the exports",
"return module.exports;"
])};`
]);
});
2.3 动态加载与代码分割
对于异步 Chunk,运行时代码还会包含动态加载逻辑(如 JSONP):
// 由 MainTemplate 生成的加载函数
__webpack_require__.e = function(chunkId) {
return Promise.resolve().then(() => {
const script = document.createElement('script');
script.src = __webpack_require__.p + chunkId + ".bundle.js";
document.head.appendChild(script);
});
};
三、实际场景分析
场景 1:单入口打包
- 输入:
entry: './src/index.js' - 输出:
main.js包含完整的运行时代码和模块逻辑。
场景 2:动态导入
// 源码中使用 import()
import('./module.js').then(m => m.doSomething());
- 输出:
main.js(MainTemplate)包含__webpack_require__.e,0.bundle.js(ChunkTemplate)包含window["webpackChunk"].push()代码。
场景 3:多入口
- 输入:
entry: { a: './a.js', b: './b.js' } - 输出:
a.js和b.js各自包含独立的运行时代码(除非配置optimization.runtimeChunk提取公共 Runtime)。
四、总结
- MainTemplate 是入口 Chunk 的代码骨架,承载运行时的核心逻辑。
- ChunkTemplate 是异步/分割 Chunk 的生成器,依赖主 Runtime 的加载能力。
- 运行时代码 通过插件系统动态注入,确保模块化机制的正确运行。