原文首发于 blog.flqin.com。若有错误,请联系笔者。分析码字不易,转载请代表出处,谢谢!html
runLoaders
运行结束后,在回调里执行了 createSource
后,判断 loader
的 result
是否有第三个参数对象而且里面存在 webpackAST
属性,若是有则为 ast
赋值到 _ast
上。node
而后回到 this.doBuild
执行回调,在根据项目配置项判断是否须要 parse
后,若须要解析,则执行:webpack
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options
},
(err, result) => {
//...
}
);
if (result !== undefined) {
// parse is sync
handleParseResult(result);
}
复制代码
this.parser
便是在 reslove 流程
里的组合对象里获得的 parser
。git
this.parser.parse
,在该方法里若是 this._ast
不存在则传 this._source._value
即代码字符串。 而后进入文件 node_modules/webpack/lib/Parser.js
执行 Parser.parse
。github
方法里执行:web
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
复制代码
Parser.parse
即为 Parser
静态方法,该方法里主要执行:json
ast = acornParser.parse(code, parserOptions); //即 acorn.Parser
复制代码
webpack
经过 acorn 获得源码对应的 ast
。ast
相关资料:api
回到 Parser.parse
,对 ast
进行遍历,执行:数组
if (this.hooks.program.call(ast, comments) === undefined) {
this.detectStrictMode(ast.body);
this.prewalkStatements(ast.body);
this.blockPrewalkStatements(ast.body);
this.walkStatements(ast.body);
}
复制代码
this.hooks.program.call(ast, comments)
app
触发回调 plugin
(HarmonyDetectionParserPlugin
和 UseStrictPlugin
) 根据是否有 import/export
和 use strict
增长依赖:HarmonyCompatibilityDependency
, HarmonyInitDependency
,ConstDependency
this.detectStrictMode(ast.body)
检测当前执行块是否有 use strict
,并设置 this.scope.isStrict = true
this.prewalkStatements(ast.body)
import
进来的变量,是 import
就增长依赖 HarmonyImportSideEffectDependency
,HarmonyImportSpecifierDependency
;export
出去的变量,是 export
增长依赖 HarmonyExportHeaderDependency
,HarmonyExportSpecifierDependency
this.blockPrewalkStatements(ast.body)
处理块遍历
this.walkStatements(ast.body)
用于深刻函数内部(方法在 walkFunctionDeclaration
进行递归),而后递归继续查找 ast
上的依赖,异步此处深刻会增长依赖 ImportDependenciesBlock
;
上述执行结束后,会根据 import/export
的不一样状况即模块间的相互依赖关系,在对应的 module.dependencies
上增长相应的依赖。
在后面 generate
即 render
阶段,调用这些依赖(Dependency
)对应的 template.apply
来渲染生成代码资源。
以 demo
入口文件 a.js
和 c.js
为例,则依赖为:
//a.js module
{
"dependencies": [
"HarmonyCompatibilityDependency", //对应模板 `HarmonyExportDependencyTemplate` 会在源码里最前面添加如:`__webpack_require__.r(__webpack_exports__);` 的代码,用于定义 exports:__esModule
"HarmonyInitDependency", // 对应模板 `HarmonyInitDependencyTemplate`, 下文单独说明其做用
"ConstDependency", // 对应模板 `ConstDependencyTemplate` 操做会在源码里将同步 import 语句删掉
"HarmonyImportSideEffectDependency", //对应模板 `HarmonyImportSideEffectDependencyTemplate`,执行 apply 调用父类 HarmonyImportDependencyTemplate 的 apply,即为空。
"HarmonyImportSpecifierDependency" //对应模板 `HarmonyImportSpecifierDependencyTemplate`,会在源码里将引入的变量替换为 webpack 对应的包装变量
],
"blocks": ["ImportDependenciesBlock"] //异步模块 对应模板 `ImportDependencyTemplate`, 会在源码里将本 demo 中的 `import('./c.js')`替换为 `Promise.resolve(/*! import() */).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js"))`
}
复制代码
//d.js module
{
"dependencies": [
"HarmonyCompatibilityDependency",
"HarmonyInitDependency",
"HarmonyExportHeaderDependency", // 对应模板 `HarmonyExportDependencyTemplate` 会在源码里将关键字 export 删掉
"HarmonyExportSpecifierDependency" //对应模板 `HarmonyExportSpecifierDependencyTemplate` 执行 apply 为空
],
"blocks": []
}
复制代码
执行其对应 template.apply
(文件 HarmonyInitDependency.js
)中,先遍历 module.dependencies
,判断各依赖对应的 template
是否包含 harmonyInit
和 getHarmonyInitOrder
函数(用于导入的 import
排序),若都存在,则执行:
const order = template.getHarmonyInitOrder(dependency);
复制代码
执行对应的 template.getHarmonyInitOrder
用于获取排序的 order
,在不一样的依赖里根据须要可能会返回 NaN
(如 HarmonyImportSideEffectDependencyTemplate
里判断无反作用(sideEffects
)就会返回 NaN
),最终筛选出不是 NaN
的依赖组成数组 list
,即为含有 import
和 export
的依赖,按 order
排序后, 执行:
for (const item of list) {
item.template.harmonyInit(item.dependency, source, runtime, dependencyTemplates);
}
复制代码
执行对应的 template.harmonyInit
,对应模板在源码前加入如下代码:
export
相关(HarmonyExportSpecifierDependency
)/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, 'mul', function() {
return mul;
});
复制代码
import
相关(HarmonyImportSideEffectDependency
,HarmonyImportSpecifierDependency
)/* harmony import */
var Src_b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! Src/b */ './src/b.js');
复制代码
parse
结束后,在 handleParseResult
里执行 this._initBuildHash(compilation)
:
_initBuildHash(compilation) {
const hash = createHash(compilation.outputOptions.hashFunction); // compilation.outputOptions.hashFunction : md4
if (this._source) {
hash.update("source");
this._source.updateHash(hash); // this._value
}
hash.update("meta");
hash.update(JSON.stringify(this.buildMeta));
this._buildHash = /** @type {string} */ (hash.digest("hex"));
}
复制代码
webpack
采用 nodejs
提供的加密模块 crypto 进行 hash
加密。
createHash()
: 即执行 new BulkUpdateDecorator(require("crypto").createHash(algorithm))
hash.update()
: 更新 hash
内容hash.digest("hex")
:获得 hash
值先初始化了 hash
, 而后分别 update
了 source
,this._value
( this._source.updateHash(hash)
得到,为文件源码),meta
,this.buildMeta
,最后计算出结果赋给 this._buildHash
。
而后回到文件 Compilation.js
的 module.build
的回调。对 error
和 warning
的处理后,对 module.dependencies
按照代码在文件中出现的前后顺序进行排序,而后触发 Compilation.hooks
: succeedModule
。
而后执行回调回到 this.buildModule
的回调里执行 afterBuild
:
const afterBuild = () => {
if (addModuleResult.dependencies) {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
复制代码
即判断若是该模块是首次解析则执行 processModuleDependencies
。
一旦某个模块被解析建立后,在 this.addModule(module)
(上文已提到)里会设置 addModuleResult.dependencies
为 false
便可以免该模块重复解析建立依赖。
在 processModuleDependencies
里,对 mudule
的 dependencies
, blocks
(懒加载 import xx
会存入), variables
(内部变量 __resourceQuery
)分别处理,其中对 blocks
的处理会递归调用。整理过滤没有标识 Identifier
的 module
,获得 sortedDependencies
(以 module a
为例):
sortedDependencies = [
{
factory: NormalModuleFactory,
dependencies: [HarmonyImportSideEffectDependency, HarmonyImportSpecifierDependency]
},
{
factory: NormalModuleFactory,
dependencies: [ImportDependency]
}
];
复制代码
而后调用 this.addModuleDependencies
:
addModuleDependencies(module, dependencies, bail, cacheGroup, recursive, callback) {
// dependencies 即为上文中的 sortedDependencies
//...
asyncLib.forEach(
dependencies,
(item, callback) => {
//...
const semaphore = this.semaphore;
semaphore.acquire(() => {
const factory = item.factory;
factory.create(
{
//...
},(err, dependentModule) => {
// 回调内容
}
);
});
},
err => {
//...
return process.nextTick(callback);
}
);
}
复制代码
经过 asyncLib.forEach forEach
会将回调传给 iterator
,在出现 err
或 iterator
所有执行后执行回调。
批量调用每一个依赖的 NormalModuleFactory.create
,即与前文moduleFactory.create
功能一致。因此重复开始走 reslove 流程
:
NormalModuleFactory.create -> resolve流程 -> 初始化module -> add module -> module build -> afterBuild -> processModuleDependencies
复制代码
就这样,从入口 module
开始,根据 module
间的依赖关系,递归调用将全部的 module
都转换编译。
在依赖转换完成后,执行:
return process.nextTick(callback);
复制代码
将在 nodejs
下一次事件循环时调用 callback
即执行 this.processModuleDependencies
的回调:
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
复制代码
此时返回一个入口 module
:
{
"module": {
//...
//同步模块
"dependencies": ["HarmonyImportSideEffectDependency", "HarmonyImportSpecifierDependency"],
//异步模块
"blocks": ["ImportDependenciesBlock"]
}
}
复制代码
执行 this._addModuleChain
的回调,触发 compilation.hooks
:succeedEntry
, 到此 mudule
生成结束!
parser
将前面 runloaders
的编译结果经过 acorn
转换为 ast
;ast
根据导入导出及异步的状况触发相关钩子插件收集依赖,这些依赖用于解析递归依赖和模板操做;module
的相关信息生成各自惟一的 buildHash
;module
间的相互依赖关系,递归解析全部依赖 module
,最终返回一个入口 module
。