原文首發於 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
。