为了更直接地解析 Webpack 中 ModuleFactory
的源码实现,我们以 Webpack 5 的源码为例,深入分析其核心逻辑。以下是关键源码文件及流程的逐步解读:
一、源码定位
-
ModuleFactory
基类文件路径:
lib/ModuleFactory.js
定义模块工厂的抽象接口,核心方法是
create
。 -
NormalModuleFactory
文件路径:
lib/NormalModuleFactory.js
处理普通模块(如 JS、CSS 文件)的工厂。
-
ContextModuleFactory
文件路径:
lib/ContextModuleFactory.js
处理上下文依赖(如
require.context
)的工厂。
二、核心源码解析:NormalModuleFactory
1. create
方法入口
源码位置:lib/NormalModuleFactory.js#L397
javascript
create(data, callback) {
const context = data.context; // 模块上下文(如项目根目录)
const contextInfo = data.contextInfo; // 上下文信息(如 issuer 模块)
const request = data.request; // 模块请求路径(如 './a.js')
const dependencies = data.dependencies; // 依赖对象
// 触发 beforeResolve 钩子(插件可在此拦截)
this.hooks.beforeResolve.callAsync(
{ contextInfo, context, request, dependencies },
(err, result) => {
if (result) return callback(null, result); // 插件直接返回结果
// 继续解析模块...
this.hooks.factorize.callAsync(/* ... */);
}
);
}
2. 解析模块路径与 Loaders
源码位置:lib/NormalModuleFactory.js#L513
javascript
this.hooks.resolve.callAsync(
{ contextInfo, context, request, fileDependencies, missingDependencies },
(err, result) => {
if (err) return callback(err);
const resource = result.resource; // 解析后的绝对路径(如 /project/a.js)
const loaders = result.loaders; // 匹配到的 Loaders(如 ['babel-loader'])
// 创建模块对象
const module = this.createModule({
loaders,
resource,
// ...
});
callback(null, module);
}
);
3. 创建 NormalModule
实例
源码位置:lib/NormalModuleFactory.js#L715
javascript
createModule(data) {
const { loaders, resource } = data;
// 实例化 NormalModule(普通模块)
return new NormalModule({
loaders, // 使用的 Loaders
resource, // 模块绝对路径
type: "javascript/auto", // 模块类型
parser: this.getParser(), // AST 解析器(如解析 JS 语法)
generator: this.getGenerator(), // 代码生成器
resolveOptions: this.resolver.options // 解析器配置
});
}
三、关键钩子(Hooks)分析
Webpack 使用 Tapable
管理钩子,以下为 NormalModuleFactory
的关键钩子:
-
beforeResolve
在解析模块路径前触发,插件可在此拦截或修改请求。
示例:禁止解析某些模块。
javascriptcompiler.hooks.normalModuleFactory.tap("MyPlugin", (nmf) => { nmf.hooks.beforeResolve.tap("MyPlugin", (data) => { if (data.request === "forbidden-module") return false; // 拦截 }); });
-
afterResolve
在解析路径后触发,可修改 Loaders 或资源信息。
示例:强制添加 Loader。
javascriptnmf.hooks.afterResolve.tap("MyPlugin", (data) => { data.loaders.push({ loader: "my-loader" }); });
-
createModule
在创建
NormalModule
实例前触发,可替换模块类。示例:自定义模块类。
javascriptnmf.hooks.createModule.tap("MyPlugin", (data) => { return new MyCustomModule(data); // 替换为自定义模块 });
四、源码流程图解
以解析 import './a.js'
为例:
text
1. Compilation 发现模块请求 './a.js'
│
2. 调用 NormalModuleFactory.create()
│
3. 触发 beforeResolve 钩子
│
4. 解析路径(使用 enhanced-resolve)
│
5. 匹配 Loaders(根据 webpack.config rules)
│
6. 触发 afterResolve 钩子
│
7. 创建 NormalModule 实例
│
8. 将模块加入 ModuleGraph
五、实战:Debug 源码
若想直观查看源码运行过程,可按以下步骤调试:
-
克隆 Webpack 仓库
bashgit clone https://github.com/webpack/webpack.git cd webpack npm install
-
创建测试项目
新建
test-project
,添加webpack.config.js
和入口文件。 -
在 VSCode 中调试
配置
launch.json
,添加调试配置:json{ "type": "node", "request": "launch", "name": "Debug Webpack", "program": "${workspaceFolder}/node_modules/webpack/bin/webpack.js", "args": ["--config", "webpack.config.js"] }
-
在
NormalModuleFactory.js
中打断点在
create
、resolve
等方法内设置断点,逐步观察逻辑。
六、总结
ModuleFactory 源码的核心逻辑:
- 路径解析 :通过
enhanced-resolve
确定模块绝对路径。 - Loader 匹配 :根据配置的
rules
选择 Loaders。 - 模块实例化 :生成
NormalModule
对象,包含源码、Loader、解析器等。 - 钩子机制 :通过
Tapable
钩子允许插件干预每个环节。
通过直接分析源码,能更深入理解 Webpack 的模块化构建机制。若需进一步探索,可关注以下文件:
lib/Compiler.js
:编译入口。lib/Compilation.js
:管理模块构建过程。lib/NormalModule.js
:普通模块的实现。