webpack 4 源碼主流程分析(九):優化 chunk

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

chunk 的一些優化

接上文,在 seal 裏,chunk 生成後,開始進行 chunk 優化之類的處理。java

在觸發鉤子 optimize,optimizeModulesmodule 相關的優化)等以後,忽略掉本次打包未觸發插件的鉤子,執行:webpack

this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups);
複製代碼

觸發插件:git

  • EnsureChunkConditionsPlugin 處理 chunkCondition
  • RemoveEmptyChunksPlugin 移除空 chunk
  • MergeDuplicateChunksPlugin 處理重複 chunk
this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups);
複製代碼

觸發插件:github

  • SplitChunksPlugin 優化切割 chunk
  • RemoveEmptyChunksPlugin 再次移除空 chunk

設置 module.id

this.hooks.reviveModules.call(this.modules, this.records);
複製代碼

觸發插件 RecordIdsPlugin:設置 module.idweb

this.hooks.beforeModuleIds.call(this.modules);
複製代碼

觸發插件 NamedModulesPlugin: 設置 module.id 爲 文件相對路徑,而後執行:算法

this.applyModuleIds();
複製代碼

這一步主要用於設置 module.id(若是 id 在上一步沒有設置的話),內部具體算法爲:json

先遍歷各 module,找出其中最大的 id 以他爲最大值(usedIdmax),計算出比他小的全部未使用的正整數和(usedIdmax+1)做爲 unusedIds 用於給沒有設置 idmodule 使用,unusedIds 用盡後,則設置 id(usedIdmax+1)++bootstrap

this.sortItemsWithModuleIds();
複製代碼

根據 module.idmodule,chunk,reasons 等排序。app

設置 chunk.id

this.hooks.reviveChunks.call(this.chunks, this.records);
複製代碼

觸發插件 RecordIdsPlugin:設置 chunk.id

this.hooks.optimizeChunkOrder.call(this.chunks);
複製代碼

觸發插件 OccurrenceOrderChunkIdsPluginchunks 排序

this.hooks.beforeChunkIds.call(this.chunks);
複製代碼

觸發插件 NamedChunksPlugin:設置 chunk.id = chunk.name

this.applyChunkIds();
複製代碼

這一步主要用於設置 chunk.id,算法與 this.applyModuleIds 一致。

this.sortItemsWithChunkIds();
複製代碼

根據 chunk.idmodule,chunk,reasons,errors,warnings,children 等排序。

if (shouldRecord) {
  this.hooks.recordModules.call(this.modules, this.records);
  this.hooks.recordChunks.call(this.chunks, this.records);
}
複製代碼

依舊是對 records 的一些設置。

建立 hash

接下來執行:

this.hooks.beforeHash.call();
this.createHash();
this.hooks.afterHash.call();
if (shouldRecord) {
  this.hooks.recordHash.call(this.records);
}
複製代碼

進入 createHash,前文已介紹生成 hash 的方法,此處先初始化一個 hash,而後執行:

this.mainTemplate.updateHash(hash);
this.chunkTemplate.updateHash(hash);
複製代碼
  • mainTemplate: 渲染生成包含 webpack runtime bootstrap 代碼的 chunk
  • chunkTemplate: 渲染生成普通 chunk

mainTemplateupdate('maintemplate','3') 後,觸發 MainTemplate.hooks: hash,執行插件 JsonpMainTemplatePluginWasmMainTemplatePlugin 內的相關事件,hash.buffer 更新爲 maintemplate3jsonp6WasmMainTemplatePlugin2

chunkTemplateupdate('ChunkTemplate','2') 後,觸發ChunkTemplate.hooks: hash,執行插件 JsonpChunkTemplatePlugin內的相關事件,hash.buffer 更新爲 maintemplate3jsonp6WasmMainTemplatePlugin2ChunkTemplate2JsonpChunkTemplatePlugin4webpackJsonpwindow

for (const key of Object.keys(this.moduleTemplates).sort()) {
  this.moduleTemplates[key].updateHash(hash);
}

// 如下代碼爲 complation 實例化的時候所定義
this.moduleTemplates = {
  javascript: new ModuleTemplate(this.runtimeTemplate, 'javascript'),
  webassembly: new ModuleTemplate(this.runtimeTemplate, 'webassembly')
};
複製代碼

this.moduleTemplateskey 排序後執行各自的 updateHashhash.buffer 更新爲 maintemplate3jsonp6WasmMainTemplatePlugin2ChunkTemplate2JsonpChunkTemplatePlugin4webpackJsonpwindow1FunctionModuleTemplatePlugin21

而後若是有 children,warnings,errors 也把他們的 hash 或者 message update 進去。而後執行:

建立 module hash

for (let i = 0; i < modules.length; i++) {
  const module = modules[i];
  const moduleHash = createHash(hashFunction);
  module.updateHash(moduleHash);
  module.hash = /** @type {string} */ (moduleHash.digest(hashDigest));
  module.renderedHash = module.hash.substr(0, hashDigestLength);
}
複製代碼

這裏循環初始化了每一個 modulehash,並調用了每一個 moduleupdateHash

module.updateHash(moduleHash);

//上面 module.updateHash 調用
hash.update(this._buildHash); //這裏加入了 _buildHash
super.updateHash(hash);

//上面 super 調用
hash.update(`${this.id}`);
hash.update(JSON.stringify(this.usedExports));
super.updateHash(hash);

//上面 super 調用
//調用各自 dependencies,blocks,variables的 updateHash
for (const dep of this.dependencies) dep.updateHash(hash);
for (const block of this.blocks) block.updateHash(hash);
for (const variable of this.variables) variable.updateHash(hash);
複製代碼

最終獲得 moduleHash.buffer 形如:ac01f98d10f099796d2f3d600c2592d1./src/a.jsnull0,28./src/b.jsnamespace./src/b.js29,57./src/c.jsnamespace./src/c.js58,121../github/test-loader/loader.js?number=20000!./src/e.jsnamespace../github/test-loader/loader.js?number=20000!./src/e.js./src/b.jsnamespace./src/b.jsaddaddnamespacenullnull./src/c.jsnamespace./src/c.jssubsubnamespacenullnull../github/test-loader/loader.js?number=20000!./src/e.jsnamespace../github/test-loader/loader.js?number=20000!./src/e.jsdivdivnamespacenullnull

而後最終生成出 module 各自的 hashrenderedHash

建立 chunk hash

繼續執行,先對 chunks 進行排序,而後遍歷 chunks

for (let i = 0; i < chunks.length; i++) {
  const chunk = chunks[i];
  const chunkHash = createHash(hashFunction);
  try {
    if (outputOptions.hashSalt) {
      chunkHash.update(outputOptions.hashSalt);
    }
    chunk.updateHash(chunkHash);
    const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate;
    template.updateHashForChunk(chunkHash, chunk, this.moduleTemplates.javascript, this.dependencyTemplates);
    this.hooks.chunkHash.call(chunk, chunkHash);
    chunk.hash = /** @type {string} */ (chunkHash.digest(hashDigest));
    hash.update(chunk.hash);
    chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
    this.hooks.contentHash.call(chunk);
  } catch (err) {
    this.errors.push(new ChunkRenderError(chunk, '', err));
  }
}
複製代碼

這裏循環初始化了每一個 chunkhash,並調用了每一個 chunkupdateHash

chunk.updateHash(chunkHash);

//上面 chunk.updateHash 調用
hash.update(`${this.id} `);
hash.update(this.ids ? this.ids.join(',') : '');
hash.update(`${this.name || ''} `);
for (const m of this._modules) {
  hash.update(m.hash); //此處把每一個 module 的 hash 一併加入
}
複製代碼

獲得 chunkHash.buffer 形如 bundle bundlebundle 99d78a1615d2e348fbf274adb4e0b67c4fa9f69c98e5b41607cb6354e95983c3824bbf3e0b5e82705f88a41a6741b08f2f18bdc137e8a1e8e6cc78ca7ce0caf64b500a96034ab069ecf31c34f944ede6,而後判斷 chunk 是否含有 runtime 代碼( template 判斷入口 chunk 與運行時 chunk 一致則爲 this.mainTemplate,不一致則爲 this.chunkTemplate )。

chunkTemplate

若是是 chunkTemplateupdateHashForChunk,則執行:

this.updateHash(hash); //與上文 this.chunkTemplate.updateHash(hash) 執行相同
this.hooks.hashForChunk.call(hash, chunk);
複製代碼

this.hooks.hashForChunk.call(hash, chunk) 觸發插件 JsonpChunkTemplatePlugin 相關事件, update entryModulegroup.childrenIterable

mainTemplate

若是是 mainTemplateupdateHashForChunk,則執行:

this.updateHash(hash); //與上文 this.mainTemplate.updateHash(hash) 執行相同
this.hooks.hashForChunk.call(hash, chunk);
for (const line of this.renderBootstrap('0000', chunk, moduleTemplate, dependencyTemplates)) {
  hash.update(line);
}
複製代碼

this.hooks.hashForChunk.call(hash, chunk) 觸發插件 TemplatedPathPlugin 相關事件,根據 chunkFilename 的不一樣配置,update chunk.getChunkMaps 的不一樣導出,chunk.getChunkMaps 的實現爲:

getChunkMaps(realHash) {
  const chunkHashMap = Object.create(null);
  const chunkContentHashMap = Object.create(null);
  const chunkNameMap = Object.create(null);

  for (const chunk of this.getAllAsyncChunks()) {
    chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
    for (const key of Object.keys(chunk.contentHash)) {
      if (!chunkContentHashMap[key]) {
        chunkContentHashMap[key] = Object.create(null);
      }
      chunkContentHashMap[key][chunk.id] = chunk.contentHash[key];
    }
    if (chunk.name) {
      chunkNameMap[chunk.id] = chunk.name;
    }
  }

  return {
    hash: chunkHashMap, // chunkFilename 配置爲 chunkhash的導出
    contentHash: chunkContentHashMap, // chunkFilename 配置爲 contenthash 的導出
    name: chunkNameMap // chunkFilename 配置爲 name 的導出
  };
}
複製代碼

可見各類類型的 hash 都與其餘的 不含runtime模塊即異步模塊hash 有強關聯,因此前面的 chunk 排序也就很重要。

this.renderBootstrap 用於拼接 webpack runtime bootstrap 代碼字符串。這裏至關於把每一行 runtime 代碼循環 update 進去,到此 chunk hash 生成結束。 將 chunk.hash updatehash 上。 最終獲得 chunk.hashchunk.renderedHash

建立 content hash

而後執行:

this.hooks.contentHash.call(chunk);
複製代碼

這裏觸發 JavascriptModulesPlugin 相關事件,主要做用是建立生成 chunk.contentHash.javascript,也就是 contentHash 生成相關,大致跟生成 chunk hash 一致.

最後在 createHash 裏獲得 Compilation.hashCompilation。fullhashhash 生成到此結束。chunk 相關優化到此結束。

本章小結

  1. 本章主要是對 chunk 的一些優化工做,暴露了不少相關的優化鉤子;
  2. 設置了 module.idchunk.id 並排序;
  3. 建立了 hash,包括 module hash,chunk hash,content hash
相關文章
相關標籤/搜索