webpack 4 源碼主流程分析(十):資源的構建

原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack

生成 module 資源

接上文,執行:git

this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
複製代碼

這一步用於生成 module 資源。在 createModuleAssets 裏,獲取每一個 module 屬性上的 buildInfo.assets,而後觸發 this.emitAsset 生成資源。buildInfo.assets 相關數據能夠在 loader 裏調用 api: this.emitFile 生成。github

生成 chunk 資源

這一步用於建立 chunk 資源。web

生成前的準備

manifest

執行: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,同步及含有 runtimechunk 對應 mainTemplateapi

而後執行對應的 getRenderManifest,觸發 template.hooks:renderManifest 執行插件 JavascriptModulesPlugin 相關事件獲得 render 所須要的所有信息:[{ render(), filenameTemplate, pathOptions, identifier, hash }]數組

若是是 chunkTemplate 還會觸發插件 WebAssemblyModulesPlugin 的相關事件處理 WebAssembly 相關。緩存

pathAndInfo

而後遍歷 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();
複製代碼

即執行對應 templaterender

chunkTemplate

生成主體 chunk 代碼

若是是異步 chunkrender 會執行在文件 JavascriptModulesPlugin.js 裏的 renderJavascript。方法裏先執行 Template.renderChunkModules 靜態方法:

const moduleSources = Template.renderChunkModules(chunk, m => typeof m.source === 'function', moduleTemplate, dependencyTemplates);
複製代碼
生成每一個 module 代碼

方法裏執行:

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裏,會根據依賴不一樣作相應的源碼轉化的處理。但方法裏並無直接執行源碼轉化的工做,而是將其轉化對象 pushReplaceSource.replacements 裏,轉化對象的格式爲:

注:webpack-sources 提供若干類型的 source 類,如 CachedSource, PrefixSource, ConcatSource, ReplaceSource 等。它們能夠組合使用,方便對代碼進行添加、替換、鏈接等操做。同時又含有一些 source-map 相關,updateHashapiwebpack 內部調用.

//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 代碼 chunkTemplatefileManifest.render 代碼構建完成。

mainTemplate

若是是同步 chunkrender 會執行在文件 JavascriptModulesPlugin.js 裏的 compilation.mainTemplate.render 即文件 MainTemplate.js 裏的 render

生成 runtime 代碼

方法裏執行:

const buf = this.renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates);
複製代碼

該方法獲得 webpack runtime bootstrap 代碼數組,從中會判斷是否有異步 chunk,若是有,則代碼裏還會包含異步相關的 runtime 代碼,若是還有其餘什麼延遲加載的模塊,都會在這裏處理爲相應是 runtime

包裹 runtime 與 chunk 代碼

而後執行:

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,該 hookconstructor 裏已註冊,代碼以下:

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 代碼

這裏來看 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 代碼 mainTemplatefileManifest.render 代碼構建完成。

文件名映射資源

不管是同步仍是一部,最後都回到 Compilation.jscreateChunkAssets 裏,作了 source 緩存,而後執行:

this.emitAsset(file, source, assetInfo);
複製代碼

創建起了文件名與對應源碼的聯繫,將該映射對象掛載到 compilation.assets 下。 而後設置了 alreadyWrittenFiles 這個 Map 對象,防止重複構建代碼。到此一個 chunk 的資源構建結束。

chunk 遍歷結束後,獲得 compilation.assetscompilation.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
      }
    }
  }
}
複製代碼

本章小結

  1. 經過 this.emitFile 可生成 module 資源,若是有則直接調用 this.emitAsset 生成資源;
  2. 生成 chunk 資源時,先根據是否含有 runtime 獲得不一樣的 template,包括 chunkTemplatemainTemplate;
  3. 經過不一樣的 template 獲得不一樣的 manifestpathAndInfo,而後調用不一樣的 render 渲染代碼;
  4. 最後創建文件名與資源之間的映射,最終一塊兒掛載到 compilation.assets 即目標資源。
相關文章
相關標籤/搜索