create: 准备数据,生成 module 实例。
add: 信息保存到 Compilation 实例上。
build: 分析文件内容。
processDep: 处理3步骤中解析得到的依赖,添加到编译链条中。
// a.js
export const A = 'a'
// demo.js,webpack 入口文件
import { A } from './a.js'
function test() {
const tmp = 'something'
return tmp + A
}
const r = test()
create(data, callback) {
//...省略部分逻辑
this.hooks.beforeResolve.callAsync(
{
contextInfo,
resolveOptions,
context,
request,
dependencies
},
(err, result) => {
//...
// 触发 normalModuleFactory 中的 factory 事件。
const factory = this.hooks.factory.call(null);
// Ignored
if (!factory) return callback();
factory(result, (err, module) => {
//...
callback(null, module);
});
}
);
}
resolver 阶段:得到 demo.js 的路径信息以及涉及到的 loader 和 loader 的路径(详细过程参考 resolver 和 loader)。这一步完成后,生成 module 的准备工作已经完成。
createModule 阶段:生成一个 module 实例,将上一步的数据存入实例中。
// moduleFactory.create 的 callback 函数
(err, module) => {
//...
let afterFactory;
//...
// addModule 会执行 this._modules.set(identifier, module); 其中 identifier 对于 normalModule 来说就是 module.request,即文件的绝对路径
// 和 this.modules.push(module);
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
// 对于入口文件来说,这里会执行 this.entries.push(module);
onModule(module);
dependency.module = module;
module.addReason(null, dependency);
//... 开始 build 阶段
}
// NormalModule.build 方法
build(options, compilation, resolver, fs, callback) {
//...
return this.doBuild(options, compilation, resolver, fs, err => {
//...
try {
// 这里会将 source 转为 AST,分析出所有的依赖
const result = this.parser.parse(/*参数*/);
if (result !== undefined) {
// parse is sync
handleParseResult(result);
}
} catch (e) {
handleParseError(e);
}
})
}
// NormalModule.doBuild 方法
doBuild(options, compilation, resolver, fs, callback) {
//...
// 执行各种 loader
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
},
(err, result) => {
//...
// createSource 会将 runLoader 得到的结果转为字符串以便后续处理
this._source = this.createSource(
this.binary ? asBuffer(source) : asString(source),
resourceBuffer,
sourceMap
);
//...
}
);
}
parse(source, initialState) {
let ast;
let comments;
if (typeof source === "object" && source !== null) {
ast = source;
comments = source.comments;
} else {
comments = [];
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
}
const oldScope = this.scope;
const oldState = this.state;
const oldComments = this.comments;
// 设置 scope,可以理解为和代码中个作用域是一致的
this.scope = {
topLevelScope: true,
inTry: false,
inShorthand: false,
isStrict: false,
definitions: new StackedSetMap(),
renames: new StackedSetMap()
};
const state = (this.state = initialState || {});
this.comments = comments;
// 遍历 AST,找到所有依赖
if (this.hooks.program.call(ast, comments) === undefined) {
this.detectStrictMode(ast.body);
this.prewalkStatements(ast.body);
this.walkStatements(ast.body);
}
this.scope = oldScope;
this.state = oldState;
this.comments = oldComments;
return state;
}
runLoaders({...}, (err, result) => {
//...省略其他内容
const source = result.result[0];
const sourceMap = result.result.length >= 1 ? result.result[1] : null;
const extraInfo = result.result.length >= 2 ? result.result[2] : null;
//...
this._ast =
typeof extraInfo === "object" &&
extraInfo !== null &&
extraInfo.webpackAST !== undefined
? extraInfo.webpackAST
: null;
})
const result = this.parser.parse(
this._ast || this._source.source(),
//...
)
prewalkStatements(statements) {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.prewalkStatement(statement);
}
}
prewalkStatement(statement) {
switch (statement.type) {
case "BlockStatement":
this.prewalkBlockStatement(statement);
break;
//...
}
}
prewalkImportDeclaration(statement) {
// source 值为 './a.js'
const source = statement.source.value;
this.hooks.import.call(statement, source);
// 如果原始代码为 import x, {y} from './a.js',则 statement.specifiers 包含 x 和 { y } ,也就是我们导入的值
for (const specifier of statement.specifiers) {
const name = specifier.local.name; // 这里是 import { A } from './a.js' 中的 A
// 将 A 写入 renames 和 definitions
this.scope.renames.set(name, null);
this.scope.definitions.add(name);
switch (specifier.type) {
case "ImportDefaultSpecifier":
this.hooks.importSpecifier.call(statement, source, "default", name);
break;
case "ImportSpecifier":
this.hooks.importSpecifier.call(
statement,
source,
specifier.imported.name,
name
);
break;
case "ImportNamespaceSpecifier":
this.hooks.importSpecifier.call(statement, source, null, name);
break;
}
}
}
parser.hooks.importSpecifier.tap(
"HarmonyImportDependencyParserPlugin",
(statement, source, id, name) => {
// 删除 A
parser.scope.definitions.delete(name);
// 然后将 A 设置为 import var
parser.scope.renames.set(name, "imported var");
if (!parser.state.harmonySpecifier)
parser.state.harmonySpecifier = new Map();
parser.state.harmonySpecifier.set(name, {
source,
id,
sourceOrder: parser.state.lastHarmonyImportOrder
});
return true;
}
);
prewalkFunctionDeclaration(statement) {
if (statement.id) {
// 将 function 的名字,test 添加到 renames 和 definitions 中
this.scope.renames.set(statement.id.name, null);
this.scope.definitions.add(statement.id.name);
}
}
walkStatements
walkFunctionDeclaration(statement) {
const wasTopLevel = this.scope.topLevelScope;
this.scope.topLevelScope = false;
for (const param of statement.params) this.walkPattern(param);
// inScope 方法会生成一个新的 scope,用于对函数的遍历。在这个新的 scope 中会将函数的参数名 和 this 记录到 renames 中。
this.inScope(statement.params, () => {
if (statement.body.type === "BlockStatement") {
this.detectStrictMode(statement.body.body);
this.prewalkStatement(statement.body);
this.walkStatement(statement.body);
} else {
this.walkExpression(statement.body);
}
});
this.scope.topLevelScope = wasTopLevel;
}
walkIdentifier(expression) {
// expression.name = A
if (!this.scope.definitions.has(expression.name)) {
const hook = this.hooks.expression.get(
this.scope.renames.get(expression.name) || expression.name
);
if (hook !== undefined) {
const result = hook.call(expression);
if (result === true) return;
}
}
}
// 删除 A
parser.scope.definitions.delete(name);
// 然后将 A 设置为 import var
parser.scope.renames.set(name, "imported var");
parser.hooks.expression
.for("imported var")
.tap("HarmonyImportDependencyParserPlugin", expr => {
const name = expr.name;// A
// parser.state.harmonySpecifier 会在 prewalk 阶段写入
const settings = parser.state.harmonySpecifier.get(name);
// 增加一个 HarmonyImportSpecifierDependency 依赖
const dep = new HarmonyImportSpecifierDependency(
settings.source,
parser.state.module,
settings.sourceOrder,
parser.state.harmonyParserScope,
settings.id,
name,
expr.range,
this.strictExportPresence
);
dep.shorthand = parser.scope.inShorthand;
dep.directImport = true;
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
});
parse总结
将 source 转为 AST(如果 source 是字符串类型)。
遍历 AST,遇到 import 语句就增加相关依赖,代码中出现 A(import 导入的变量) 的地方也增加相关的依赖。('use strict'的依赖和我们 module 生成的主流程无关,这里暂时忽略)。
HarmonyCompatibilityDependency
HarmonyInitDependency
ConstDependency
HarmonyImportSideEffectDependency
HarmonyImportSpecifierDependency
const afterBuild = () => {
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
// 如果有依赖,则进入 processModuleDependencies
if (addModuleResult.dependencies) {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
processModuleDependencies(module, callback) {
const dependencies = new Map();
// 整理 dependency
const addDependency = dep => {
const resourceIdent = dep.getResourceIdentifier();
// 过滤掉没有 ident 的,例如 constDependency 这些只用在最后打包文件生成的依赖
if (resourceIdent) {
// dependencyFactories 中记录了各个 dependency 对应的 ModuleFactory。
// 还记得前一篇文章中介绍的处理入口的 xxxEntryPlugin 吗?
// 在 compilation 事的回调中会执行 `compilation.dependencyFactories.set` 方法。
// 类似的,ImportPlugin,ConstPlugin 等等,也会在 compilation 事件回调中执行 set 操作,
// 将 dependency 与用来处理这个 dependency 的 moduleFactory 对应起来。
const factory = this.dependencyFactories.get(dep.constructor);
if (factory === undefined)
throw new Error(
`No module factory available for dependency type: ${
dep.constructor.name
}`
);
let innerMap = dependencies.get(factory);
if (innerMap === undefined)
dependencies.set(factory, (innerMap = new Map()));
let list = innerMap.get(resourceIdent);
if (list === undefined) innerMap.set(resourceIdent, (list = []));
list.push(dep);
}
};
const addDependenciesBlock = block => {
if (block.dependencies) {
iterationOfArrayCallback(block.dependencies, addDependency);
}
if (block.blocks) {
iterationOfArrayCallback(block.blocks, addDependenciesBlock);
}
if (block.variables) {
iterationBlockVariable(block.variables, addDependency);
}
};
try {
addDependenciesBlock(module);
} catch (e) {
callback(e);
}
const sortedDependencies = [];
// 将上面的结果转为数组形式
for (const pair1 of dependencies) {
for (const pair2 of pair1[1]) {
sortedDependencies.push({
factory: pair1[0],
dependencies: pair2[1]
});
}
}
this.addModuleDependencies(/*参数*/);
}
block 依赖
//...省略其他逻辑
else if (expression.callee.type === "Import") {
result = this.hooks.importCall.call(expression);
//...
}
//...
// ImportParserPlugin
const depBlock = new ImportDependenciesBlock(
param.string,
expr.range,
Object.assign(groupOptions, {
name: chunkName
}),
parser.state.module,
expr.loc,
parser.state.module
);
// parser.state.current 为当前处理的 module
parser.state.current.addBlock(depBlock);
variables 依赖
// main.js
require('./a.js?test')
// a.js
const a = __resourceQuery
console.log(a)
dependencies = {
NormalModuleFactory: {
"module./a.js": [
HarmonyImportSideEffectDependency,
HarmonyImportSpecifierDependency
]
}
}
sortedDependencies = [
{
factory: NormalModuleFactory,
dependencies: [
HarmonyImportSideEffectDependency,
HarmonyImportSpecifierDependency
]
}
]
_preparedEntrypoints:
\
module: demo.js module
|\
| HarmonyImportSideEffectDependency
| module: a.js module
\
HarmonyImportSpecifierDependency
module: a.ja module
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
//...
this.hooks.make.callAsync(compilation, err => {
// 回到这个回调中
if (err) return callback(err);
compilation.finish();
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});