原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack
接上文,執行:git
this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
複製代碼
這一步用於生成 module
資源。在 createModuleAssets
裏,獲取每一個 module
屬性上的 buildInfo.assets
,而後觸發 this.emitAsset
生成資源。buildInfo.assets
相關數據能夠在 loader
裏調用 api
: this.emitFile
生成。github
這一步用於建立 chunk
資源。web
執行:json
this.hooks.beforeChunkAssets.call();
this.createChunkAssets();
複製代碼
在 createChunkAssets
裏循環對每一個 chunk
執行:bootstrap
//...
const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate;
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
}); //獲得 `render` 所須要的所有信息:`[{ render(), filenameTemplate, pathOptions, identifier, hash }]`
//...
複製代碼
在判斷 chunk
是否含有 runtime
代碼後即同步異步後,獲取到對應的 template
。異步 chunk
對應 chunkTemplate
,同步及含有 runtime
的 chunk
對應 mainTemplate
。api
而後執行對應的 getRenderManifest
,觸發 template.hooks:renderManifest
執行插件 JavascriptModulesPlugin
相關事件獲得 render
所須要的所有信息:[{ render(), filenameTemplate, pathOptions, identifier, hash }]
。數組
若是是 chunkTemplate
還會觸發插件 WebAssemblyModulesPlugin
的相關事件處理 WebAssembly
相關。緩存
而後遍歷 manifest
對象執行:app
const pathAndInfo = this.getPathWithInfo(filenameTemplate, fileManifest.pathOptions);
複製代碼
this.getPathWithInfo
用於獲得路徑和相關信息。會觸發 mainTemplate.hooks
:assetPath
,去執行插件 TemplatedPathPlugin
相關事件,使用若干 replace
將如 [name].[chunkhash:8].js
替換爲 0.e3296d88.js
。
而後判斷有無 source
緩存後,若無則執行:
source = fileManifest.render();
複製代碼
即執行對應 template
的 render
。
若是是異步 chunk
,render
會執行在文件 JavascriptModulesPlugin.js
裏的 renderJavascript
。方法裏先執行 Template.renderChunkModules
靜態方法:
const moduleSources = Template.renderChunkModules(chunk, m => typeof m.source === 'function', moduleTemplate, dependencyTemplates);
複製代碼
方法裏執行:
const allModules = modules.map(module => {
return {
id: module.id,
source: moduleTemplate.render(module, dependencyTemplates, {
chunk
})
};
});
複製代碼
這裏循環對每個 module
執行 render
,方法裏執行:
const moduleSource = module.source(dependencyTemplates, this.runtimeTemplate, this.type);
//...
複製代碼
module.source
裏執行:
const source = this.generator.generate(this, dependencyTemplates, runtimeTemplate, type);
複製代碼
這個 generator
就是在 reslove 流程 -> getGenerator
所得到,即執行:
this.sourceBlock(module, module, [], dependencyTemplates, source, runtimeTemplate);
複製代碼
這裏循環處理 module
的每一個依賴(module.dependencies
):得到依賴所對應的 template
模板類,而後執行該類的 apply
:
const template = dependencyTemplates.get(dependency.constructor);
//...
template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
複製代碼
這裏的 dependencyTemplates
就是在 reslove 流程前的準備 ->Compiler.compile ->實例化 compilation
裏添加的依賴模板模塊。
在 apply
裏,會根據依賴不一樣作相應的源碼轉化的處理。但方法裏並無直接執行源碼轉化的工做,而是將其轉化對象 push
到 ReplaceSource.replacements
裏,轉化對象的格式爲:
注:
webpack-sources
提供若干類型的source
類,如CachedSource, PrefixSource, ConcatSource, ReplaceSource
等。它們能夠組合使用,方便對代碼進行添加、替換、鏈接等操做。同時又含有一些source-map
相關,updateHash
等api
供webpack
內部調用.
//Replacement
{
"content": "__webpack_require__.r(__webpack_exports__);\n", // 替換的內容
"end": -11, // 替換源碼的終止位置
"insertIndex": 0, // 優先級
"name": "", // 名稱
"start": -10 // 替換源碼的起始位置
}
複製代碼
各模板的具體處理轉化見 構建 module(下) -> parse 源碼 -> 各依賴做用解釋
。
收集完依賴相關的轉化對象 Replacement
以後,回到 module.source
進行 cachedSource
緩存包裝後,回到 moduleTemplate.render
方法獲得 moduleSource
。
而後觸發相關 ModuleTemplate.hooks:content,module,render,package
,前兩個鉤子主要是可讓咱們完成對 module
源碼的再次處理,而後在 render
鉤子裏執行插件 FunctionModuleTemplatePlugin
的相關事件,主要是給處理後的 module
源碼進行包裹,即生成代碼:
/***/
(function(module, __webpack_exports__, __webpack_require__) {
'use strict';
//CachedSource 即爲module源碼,裏面包含 replacements
/***/
});
複製代碼
而後觸發 package
鉤子執行插件 FunctionModuleTemplatePlugin
的相關事件,主要做用是添加相關注釋,即生成代碼:
/*!***************************************************************!*\ !*** ./src/c.js ***! \***************************************************************/
/*! exports provided: sub */
/***/
(function(module, __webpack_exports__, __webpack_require__) {
'use strict';
//CachedSource 即爲module源碼,裏面包含 replacements
/***/
});
複製代碼
將全部的 module
都處理完畢後,回到 renderChunkModules
,繼續處理生成代碼,最終將每一個 module
生成的代碼串起來獲得 moduleSources
回到了 renderJavascript
裏。
方法裏先觸發 chunkTemplate.hooks : modules
爲修改生成的 chunk
代碼提供鉤子,獲得 core
後,觸發 chunkTemplate.hooks
:render
執行插件 JsonpChunkTemplatePlugin
相關事件,該事件主要是添加 jsonp
異步包裹代碼,獲得:
(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[0]
// 前面生成的 chunk 代碼
]);
複製代碼
完成後,最後返回一個 new ConcatSource(source, ";")
。到此普通的異步 chunk
代碼 chunkTemplate
的 fileManifest.render
代碼構建完成。
若是是同步 chunk
,render
會執行在文件 JavascriptModulesPlugin.js
裏的 compilation.mainTemplate.render
即文件 MainTemplate.js
裏的 render
。
方法裏執行:
const buf = this.renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates);
複製代碼
該方法獲得 webpack runtime bootstrap
代碼數組,從中會判斷是否有異步 chunk
,若是有,則代碼裏還會包含異步相關的 runtime
代碼,若是還有其餘什麼延遲加載的模塊,都會在這裏處理爲相應是 runtime
。
而後執行:
let source = this.hooks.render.call(new OriginalSource(Template.prefix(buf, ' \t') + '\n', 'webpack/bootstrap'), chunk, hash, moduleTemplate, dependencyTemplates);
複製代碼
先經過 Template.prefix
合併 runtime
代碼字符串,獲得 OriginalSource
的實例,而後將其做爲參數執行 MainTemplate.hooks
: render
,該 hook
在 constructor
裏已註冊,代碼以下:
const source = new ConcatSource();
source.add('/******/ (function(modules) { // webpackBootstrap\n');
source.add(new PrefixSource('/******/', bootstrapSource));
source.add('/******/ })\n');
source.add('/************************************************************************/\n');
source.add('/******/ (');
source.add(this.hooks.modules.call(new RawSource(''), chunk, hash, moduleTemplate, dependencyTemplates));
source.add(')');
return source;
複製代碼
該方法對 runtime bootstrap
代碼進行了包裝(bootstrapSource
即爲前面生成的 runtime
代碼),其中觸發 MainTemplate.hooks: modules
獲得 chunk
的生成代碼,即最終返回一個包含了 runtime
代碼和 chunk
代碼的 ConcatSource
實例。
這裏來看 chunk
代碼的實現,如上文代碼中:
this.hooks.modules.call(new RawSource(''), chunk, hash, moduleTemplate, dependencyTemplates);
複製代碼
這裏 mainTemplate.hooks: modules
觸發插件 JavascriptModulesPlugin
的相關事件,即執行 Template
類的靜態方法 renderChunkModules
。與前文 chunkTemplate -> 生成主體 chunk 代碼
的實現一致。
最終通過包裹後獲得的代碼大體以下:
"/******/ (function(modules) { // webpackBootstrap
// runtime 代碼的 PrefixSource 實例
/******/ })
/************************************************************************/
/******/ ({
/***/ "../github/test-loader/loader.js?number=20!./src/d.js":
/*!************************************************************!*\
!*** ../github/test-loader/loader.js?number=20!./src/d.js ***!
\************************************************************/
/*! exports provided: mul */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module d 的 CachedSource 實例
/***/ }),
/***/ "./src/a.js":
/*!******************!*\
!*** ./src/a.js ***!
\******************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module a 的 CachedSource 實例
/***/ }),
/***/ "./src/b.js":
/*!******************!*\
!*** ./src/b.js ***!
\******************/
/*! exports provided: add, addddd */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module b 的 CachedSource 實例
/***/ })
/******/ })"
複製代碼
完成後,最後返回一個 new ConcatSource(source, ";")
。到此普通的同步 chunk
代碼 mainTemplate
的 fileManifest.render
代碼構建完成。
不管是同步仍是一部,最後都回到 Compilation.js
的 createChunkAssets
裏,作了 source
緩存,而後執行:
this.emitAsset(file, source, assetInfo);
複製代碼
創建起了文件名與對應源碼的聯繫,將該映射對象掛載到 compilation.assets
下。 而後設置了 alreadyWrittenFiles
這個 Map
對象,防止重複構建代碼。到此一個 chunk
的資源構建結束。
chunk
遍歷結束後,獲得 compilation.assets
和 compilation.assetsInfo
:
//compilation
{
//...
"assets": {
"0.3e.js": CachedSource, // CachedSource 裏包含資源
"bundle.bf23.js": CachedSource
},
//...map結構
"assetsInfo": {
0: {
"key": '0.3e.js',
"value": {
immutable:true
}
},
1: {
"key": 'bundle.bf23.js',
"value": {
immutable:true
}
}
}
}
複製代碼
this.emitFile
可生成 module
資源,若是有則直接調用 this.emitAsset
生成資源;chunk
資源時,先根據是否含有 runtime
獲得不一樣的 template
,包括 chunkTemplate
和 mainTemplate
;template
獲得不一樣的 manifest
和 pathAndInfo
,而後調用不一樣的 render
渲染代碼;compilation.assets
即目標資源。