一、流程概览
Webpack 的构建阶段核心任务是将入口文件及其依赖转换为模块依赖图。以下是关键步骤的源码解析及案例演示:
步骤 | 核心对象 | 源码关键文件 | 作用 |
---|---|---|---|
1. 入口模块处理 | EntryPlugin |
EntryPlugin.js |
将配置的 entry 转换为编译入口 |
2. 模块加载 | NormalModuleFactory |
NormalModuleFactory.js |
创建模块实例并初始化 Loader |
3. 路径解析 | Resolver |
ResolverFactory.js |
解析模块绝对路径及扩展名 |
4. AST 分析 | JavascriptParser |
JavascriptParser.js |
解析代码,提取 import/require |
5. 递归处理子依赖 | Compilation |
Compilation.js |
构建完整的模块依赖图 |
二、分步源码解析
1. 入口模块处理:EntryPlugin 触发 compilation.addEntry
源码位置 :webpack/lib/EntryPlugin.js
javascript
// webpack/lib/EntryPlugin.js
class EntryPlugin {
apply(compiler) {
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
const { entry, options } = this;
compilation.addEntry(
compilation.options.context, // 上下文路径(如项目根目录)
new EntryDependency(entry), // 创建入口依赖对象
options,
(err) => {
callback(err);
}
);
});
}
}
关键逻辑:
- 触发时机 :Webpack 在
compiler.make
阶段调用EntryPlugin
。 - 入口转换 :将配置的
entry
(如./src/index.js
)转换为EntryDependency
对象。 - 入口注册 :调用
compilation.addEntry
,将入口添加到编译流程。
2. 模块加载:NormalModuleFactory 创建模块对象
源码位置 :webpack/lib/NormalModuleFactory.js
javascript
// webpack/lib/NormalModuleFactory.js
class NormalModuleFactory {
create(data, callback) {
const resolveData = {
context: data.context,
request: dependency.request, // 模块请求路径(如 './a.js')
};
// 1. 解析模块路径
this.hooks.resolve.callAsync(resolveData, (err, result) => {
// 2. 创建模块实例
const createdModule = new NormalModule({
type: "javascript/auto",
request: result.request, // 绝对路径(如 '/project/src/a.js')
userRequest: dependency.userRequest,
rawRequest: dependency.request,
loaders: result.loaders, // 配置的 Loaders(如 ['babel-loader'])
});
callback(null, createdModule);
});
}
}
关键逻辑:
- 路径解析 :调用
enhanced-resolve
解析模块绝对路径。 - Loader 配置 :合并模块对应的 Loader(如
.js
文件使用babel-loader
)。 - 模块实例化 :生成
NormalModule
对象,存储模块元信息。
3. 路径解析:Resolver 处理模块路径
源码位置 :webpack/lib/ResolverFactory.js
(依赖 enhanced-resolve
)
javascript
// webpack/lib/ResolverFactory.js
const resolver = ResolverFactory.createResolver({
fileSystem: compiler.inputFileSystem,
extensions: [".js", ".json"], // 支持的扩展名
alias: config.resolve.alias, // 路径别名(如 '@' -> './src')
});
resolver.resolve({}, context, request, (err, resolvedPath) => {
// resolvedPath 是模块的绝对路径(如 '/project/src/a.js')
});
关键逻辑:
- 路径转换 :将相对路径(
./a.js
)解析为绝对路径。 - 扩展名补全 :自动补全
.js
、.json
等扩展名。 - 别名处理 :支持 Webpack 配置的
resolve.alias
。
4. AST 分析:Parser 解析代码并提取依赖
源码位置 :webpack/lib/javascript/JavascriptParser.js
javascript
// webpack/lib/javascript/JavascriptParser.js
class JavascriptParser {
parse(source, state) {
const ast = acorn.parse(source, { ecmaVersion: 2020 }); // 使用 acorn 生成 AST
this.walkStatements(ast.body);
return state;
}
walkImportDeclaration(statement) {
const request = statement.source.value; // 提取 import 路径(如 './a.js')
const dep = new ImportDependency(request, statement.range);
state.current.addDependency(dep); // 将依赖添加到模块
}
}
关键逻辑:
- AST 生成 :使用
acorn
解析 JS 代码生成抽象语法树。 - 依赖提取 :遍历 AST 识别
import/require
语句,生成ImportDependency
对象。 - 依赖关联 :将依赖添加到当前模块的
dependencies
数组。
5. 递归处理子依赖:构建完整依赖图
源码位置 :webpack/lib/Compilation.js
javascript
// webpack/lib/Compilation.js
class Compilation {
buildModule(module, callback) {
module.build(/* ... */, (err) => {
// 模块构建完成后,处理其依赖
this.processModuleDependencies(module, (err) => {
// 递归处理子模块
module.dependencies.forEach(dep => {
const childModule = this.addModule(dep);
this.buildModule(childModule, callback);
});
});
});
}
}
关键逻辑:
- 递归入口 :在模块构建完成后,调用
processModuleDependencies
。 - 子模块处理 :遍历模块的
dependencies
,对每个子模块重复addModule
和buildModule
流程。 - 依赖图生成:最终形成一个树状的模块依赖图。
三、案例演示:从入口到依赖图的完整流程
1. 项目结构
css
src/
index.js
a.js
b.js
2. 文件内容
javascript
// src/index.js
import a from './a.js';
console.log(a);
// src/a.js
import b from './b.js';
export default b + 1;
// src/b.js
export default 42;
3. 构建流程解析
步骤 | 模块 | 关键操作 |
---|---|---|
1 | index.js | - EntryPlugin 触发 addEntry - 解析为绝对路径 /project/src/index.js |
2 | index.js | - NormalModuleFactory 创建模块实例 - 通过 Loader 处理代码(如有) |
3 | index.js | - Parser 解析 AST,提取 import './a.js' |
4 | a.js | - 递归调用 addModule 和 buildModule - 解析 import './b.js' |
5 | b.js | - 继续递归处理,发现无更多依赖 |
6 | 完成 | 生成依赖图:index.js → a.js → b.js |
四、关键流程图解
scss
[EntryPlugin]
↓ 触发 compilation.addEntry
[Compilation]
↓ 调用 NormalModuleFactory.create()
[NormalModule]
↓ 使用 Resolver 解析路径
[enhanced-resolve]
↓ 返回绝对路径
[NormalModule.build()]
↓ 调用 Parser.parse() 提取依赖
[JavascriptParser]
↓ 生成 ImportDependency
[Compilation.processModuleDependencies()]
↓ 递归处理子模块
五、总结
Webpack 的构建阶段通过模块工厂、路径解析、AST 分析、递归处理四大核心机制,将入口文件逐步转换为完整的模块依赖图。理解这一流程有助于:
- 优化构建速度:减少不必要的模块解析(如缓存解析结果)。
- 调试依赖问题:通过分析 AST 和依赖关系定位错误。
- 自定义扩展:开发 Loader 或 Plugin 时,精准介入构建流程。