Webpack: 三種Chunk產物的打包邏輯

概述

  • 在前文 Webpack: Dependency Graph 管理模塊間依賴 中,我們已經詳細講解了「構建」階段如何從 Entry 開始逐步遞歸讀入、解析模塊內容,并最終構建出模塊依賴關系圖 —— ModuleGraph 對象。本文我們繼續往下,講解在接下來的「封裝」階段,如何根據 ModuleGraph 內容組織 Chunk,并進一步構建出 ChunkGroup、ChunkGraph 依賴關系對象的主流程。

主流程之外,我們還會詳細講解幾個比較模糊的概念:

  • Chunk、ChunkGroup、ChunGraph 對象分別是什么?互相之間存在怎樣的交互關系?
  • Webpack 默認分包規則,以及規則中存在的問題。

ChunkGraph 構建過程

在 前 Init、Make、Seal》中,我們已經介紹了 Webpack 底層構建邏輯大體上可以劃分為:「初始化、構建、封裝」三個階段:

在這里插入圖片描述

其中,「構建」階段負責分析模塊間的依賴關系,建立起模塊之間的 依賴關系圖(ModuleGraph);緊接著,在「封裝」階段根據依賴關系圖,將模塊分開封裝進若干 Chunk 對象中,并將 Chunk 之間的父子依賴關系梳理成 ChunkGraph 與若干 ChunkGroup 對象。

「封裝」階段最重要的目標就是根據「構建」階段收集到的 ModuleGraph 關系圖構建 ChunkGraph 關系圖,這個過程的邏輯比較復雜:

請添加圖片描述

我們簡單分析一下這里面幾個重要步驟的實現邏輯。

第一步非常關鍵: 調用 seal() 函數后,遍歷 entry 配置,為每個入口創建一個空的 Chunk 與 EntryPoint 對象(一種特殊的 ChunkGroup),并初步設置好基本的 ChunkGraph 結構關系,為下一步驟做好準備,關鍵代碼:

class Compilation {seal(callback) {// ...const chunkGraphInit = new Map();// 遍歷入口模塊列表for (const [name, { dependencies, includeDependencies, options }] of this.entries) {// 為每一個 entry 創建對應的 Chunk 對象const chunk = this.addChunk(name);// 為每一個 entry 創建對應的 ChunkGroup 對象const entrypoint = new Entrypoint(options);// 關聯 Chunk 與 ChunkGroupconnectChunkGroupAndChunk(entrypoint, chunk);// 遍歷 entry Dependency 列表for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {// 為每一個 EntryPoint 關聯入口依賴對象,以便下一步從入口依賴開始遍歷其它模塊entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request);const module = this.moduleGraph.getModule(dep);if (module) {// 在 ChunkGraph 中記錄入口模塊與 Chunk 關系chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);// ...}}}// 調用 buildChunkGraph 方法,開始構建 ChunkGraphbuildChunkGraph(this, chunkGraphInit);// 觸發各種優化鉤子// ...}
}

執行完成后,形成如下數據結構:
在這里插入圖片描述

其次,若此時配置了 entry.runtime,Webpack 還會在這個階段為運行時代碼 創建 相應的 Chunk 并直接 分配 給 entry 對應的 ChunkGroup對象。一切準備就緒后調用 buildChunkGraph 函數,進入下一步驟。

第二步:buildChunkGraph 函數內 調用 visitModules 函數,遍歷 ModuleGraph,將所有 Module 按照依賴關系分配給不同 Chunk 對象;這個過程中若遇到 異步模塊,則為該模塊 創建新的 ChunkGroupChunk 對象,最終形成如下數據結構:
請添加圖片描述

第三步:buildChunkGraph 函數中調用 connectChunkGroups 方法,建立 ChunkGroup 之間、Chunk 之間的依賴關系,生成完整的 ChunkGraph 對象,最終形成如下數據結構:
請添加圖片描述

第四步:buildChunkGraph 函數中調用 cleanupUnconnectedGroups 方法,清理無效 ChunkGroup,主要起到性能優化作用。

自上而下經過這四個步驟后,ModuleGraph 中存儲的模塊將根據模塊本身的性質,被分配到 Entry、Async、Runtime 三種不同的 Chunk 對象,并將 Chunk 之間的依賴關系存儲到 ChunkGraph 與 ChunkGroup 集合中,后續可在這些對象基礎上繼續修改分包策略(例如 SplitChunksPlugin),通過重新組織、分配 Module 與 Chunk 對象的歸屬實現分包優化。

Chunk vs ChunkGroup vs ChunkGraph

上述構建過程涉及 Chunk、ChunkGroup、ChunkGraph 三種關鍵對象,我們先總結它們的概念與作用,加深理解:

  • Chunk:Module 用于讀入模塊內容,記錄模塊間依賴等;而 Chunk 則根據模塊依賴關系合并多個 Module,輸出成資產文件(合并、輸出產物的邏輯,我們放到下一章講解):

請添加圖片描述

  • ChunkGroup:一個 ChunkGroup 內包含一個或多個 Chunk 對象;ChunkGroupChunkGroup 之間形成父子依賴關系:

請添加圖片描述

  • ChunkGraph:最后,Webpack 會將 Chunk 之間、ChunkGroup 之間的依賴關系存儲到 compilation.chunkGraph 對象中,形成如下類型關系:
    請添加圖片描述

默認分包規則

綜合上述 ChunkGraph 構建流程最終會將 Module 組織成三種不同類型的 Chunk:

  • Entry Chunk:同一個 entry 下觸達到的模塊組織成一個 Chunk;
  • Async Chunk:異步模塊單獨組織為一個 Chunk;
  • Runtime Chunk:entry.runtime 不為空時,會將運行時模塊單獨組織成一個 Chunk。

這是 Webpack 內置的,在不使用 splitChunks 或其它插件的情況下,模塊輸入映射到輸出的默認規則,是 Webpack 底層關鍵原理之一,因此有必要展開介紹每一種 Chunk 的具體規則。

Entry Chunk:

先從 Entry Chunk 開始,Webpack 首先會為每一個 entry 創建 Chunk 對象,例如對于如下配置:

module.exports = {entry: {main: "./src/main",home: "./src/home",}
};

遍歷 entry 對象屬性并創建出 chunk[main]chunk[home] 兩個對象,此時兩個 Chunk 分別包含 mainhome 模塊:
在這里插入圖片描述

初始化完畢后,Webpack 會根據 ModuleGraph 的依賴關系數據,將 entry 下所觸及的所有 Module 塞入 Chunk (發生在 visitModules 方法),比如對于如下文件依賴:
在這里插入圖片描述

main.js 以同步方式直接或間接引用了 a/b/c/d 四個文件,Webpack 會首先為 main.js 模塊創建 Chunk 與 EntryPoint 對象,之后將 a/b/c/d 模塊逐步添加到 chunk[main] 中,最終形成:
在這里插入圖片描述

Async Chunk:

其次,Webpack 會將每一個異步導入語句(import(xxx)require.ensure)處理為一個單獨的 Chunk 對象,并將其子模塊都加入這個 Chunk 中 —— 我們稱之為 Async Chunk。例如對于下面的例子:

// index.js
import './sync-a.js'
import './sync-b.js'import('./async-a.js')// async-a.js
import './sync-c.js'

在入口模塊 index.js 中,以同步方式引入 sync-a、sync-b;以異步方式引入 async-a 模塊;同時,在 async-a 中以同步方式引入 sync-c 模塊,形成如下模塊依賴關系圖:
請添加圖片描述

此時,Webpack 會為入口 index.js、異步模塊 async-a.js 分別創建分包,形成如下 Chunk 結構:
在這里插入圖片描述

并且 chunk[index]chunk[async-a] 之間形成了單向依賴關系,Webpack 會將這種依賴關系保存在 ChunkGroup._parentsChunkGroup._children 屬性中。

Runtime Chunk:

最后,除了 entry、異步模塊外,Webpack5 還支持將 Runtime 代碼單獨抽取為 Chunk。這里說的 Runtime 代碼是指一些為了確保打包產物能正常運行,而由 Webpack 注入的一系列基礎框架代碼,舉個例子,常見的 Webpack 打包產物結構如:
請添加圖片描述
上圖紅框圈出來的一大段代碼就是 Webpack 動態生成的運行時代碼,編譯時,Webpack 會根據業務代碼,決定輸出哪些支撐特性的運行時代碼(基于 Dependency 子類),例如:

  • 需要 __webpack_require__.f__webpack_require__.r 等功能實現最起碼的模塊化支持;
  • 如果用到動態加載特性,則需要寫入 __webpack_require__.e 函數;
  • 如果用到 Module Federation 特性,則需要寫入 __webpack_require__.o 函數;
  • 等等。

雖然每段運行時代碼可能都很小,但隨著特性的增加,最終結果會越來越大,特別對于多 entry 應用,在每個入口都重復打包一份相似的運行時顯得有點浪費,為此 Webpack5 提供了 entry.runtime 配置項用于聲明如何打包運行時代碼。用法上只需在 entry 項中增加字符串形式的 runtime 值,例如:

module.exports = {entry: {index: { import: "./src/index", runtime: "solid-runtime" },}
};

compilation.seal 函數中,Webpack 首先為 entry 創建 EntryPoint,之后判斷 entry 配置中是否帶有 runtime 屬性,有則創建以 runtime 值為名的 Chunk,因此,上例配置將生成兩個 Chunk:chunk[index.js]chunk[solid-runtime],并據此最終產出兩個文件:

  • 入口 index 對應的 index.js 文件;
  • 運行時配置對應的 solid-runtime.js 文件。

在多 entry 場景中,只要為每個 entry 都設定相同的 runtime 值,Webpack 運行時代碼就會合并寫入到同一個 Runtime Chunk 中,最終達成產物性能優化效果。例如對于如下配置:

module.exports = {entry: {index: { import: "./src/index", runtime: "solid-runtime" },home: { import: "./src/home", runtime: "solid-runtime" },}
};

入口 indexhome 共享相同的 runtime 值,最終生成三個 Chunk,分別為:
請添加圖片描述

此時入口 chunk[index]chunk[home] 與運行時 chunk[solid-runtime] 也會形成父子依賴關系。

分包規則的問題

默認分包規則最大的問題是無法解決模塊重復,如果多個 Chunk 同時包含同一個 Module,那么這個 Module 會被不受限制地重復打包進這些 Chunk。比如假設我們有兩個入口 main/index 同時依賴了同一個模塊:
在這里插入圖片描述

默認情況下,Webpack 不會對此做額外處理,只是單純地將 c 模塊同時打包進 main/index 兩個 Chunk,最終形成:

在這里插入圖片描述

可以看到 chunk 間互相孤立,模塊 c 被重復打包,對最終產物可能造成不必要的性能損耗!

為了解決這個問題,Webpack 3 引入 CommonChunkPlugin 插件試圖將 entry 之間的公共依賴提取成單獨的 chunk,但 CommonChunkPlugin 本質上還是基于 Chunk 之間簡單的父子關系鏈實現的,很難推斷出提取出的第三個包應該作為 entry 的父 chunk 還是子 chunkCommonChunkPlugin 統一處理為父 chunk,某些情況下反而對性能造成了不小的負面影響。

為此,在 Webpack4 之后才專門引入了更復雜的數據結構 —— ChunkGroup 專門實現關系鏈管理,配合 SplitChunksPlugin 能夠更高效、智能地實現啟發式分包。

總結

綜上,「構建」階段負責根據模塊的引用關系構建 ModuleGraph;「封裝」階段則負責根據 ModuleGraph 構建一系列 Chunk 對象,并將 Chunk 之間的依賴關系(異步引用、Runtime)組織為 ChunkGraph —— Chunk 依賴關系圖對象。與 ModuleGraph 類似,ChunkGraph 結構的引入也能解耦 Chunk 之間依賴關系的管理邏輯,整體架構邏輯更合理更容易擴展。

不過,雖然看著很復雜,但「封裝」階段最重要的目標還是在于:確定有多少個 Chunk,以及每一個 Chunk 中包含哪些 Module —— 這些才是真正影響最終打包結果的關鍵因素。

針對這一點,我們需要理解 Webpack5 內置的三種分包規則:Entry Chunk、Async Chunk 與 Runtime Chunk,這些是最最原始的分包邏輯,其它插件(例如 splitChunksPlugin)都是在此基礎,借助 buildChunkGraph 后觸發的各種鉤子進一步拆分、合并、優化 Chunk 結構,實現擴展分包效果。

思考 Chunk 一定會且只會生產出一個產物文件嗎?為什么?mini-css-extract-pluginfile-loader 這一類能寫出額外文件的組件,底層是怎么實現的?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/40252.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/40252.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/40252.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【大數據】—美國交通事故分析(2016 年 2 月至 2020 年 12 月)

引言 在當今快速發展的數字時代,大數據已成為我們理解世界、做出決策的重要工具。特別是在交通安全領域,大數據分析能夠揭示事故模式、識別風險因素,并幫助制定預防措施,從而挽救生命。本文將深入探討2016年2月至2020年12月期間&…

【redis】 LRU 和 LFU 算法

1、簡介 Redis 中的 LRU(Least Recently Used)和 LFU(Least Frequently Used)算法是用于決定在內存空間不足時,哪些鍵(key)應該被刪除以釋放空間的策略。這兩種算法都試圖通過跟蹤鍵的使用情況…

解決Memcached內存碎片:優化緩存性能的策略

解決Memcached內存碎片:優化緩存性能的策略 Memcached是一個廣泛使用的高性能分布式內存緩存系統,它通過在內存中緩存數據來加速數據檢索操作。然而,隨著時間的推移和緩存操作的進行,Memcached可能會遇到內存碎片問題&#xff0c…

24年河南特崗教師招聘流程+報名流程

河南特崗教師報名流程如下 1.登錄河南省特崗招聘網 登錄河南省特崗招聘網注冊賬號和密碼,賬號可以是手機號或者身份證號,密碼自己設置 2.注冊登錄賬號 注冊完賬號重新登錄賬號,輸入身份證號、手機號、密碼、驗證碼 3.瀏覽考試須知 填寫個人信…

Python 編程快速上手——讓繁瑣工作自動化(第2版)讀書筆記01 Python基礎快速過關

Python 編程快速上手——讓繁瑣工作自動化(第2版)讀書筆記01 Python基礎快速過關 1 python基礎概念 Python提供了高效的高級數據結構,還能簡單有效地面向對象編程。 python運算符順序 **——%——//——/——*——-——python中常見的數據…

Real-Time 3D Graphics with WebGL2

WebGL渲染管線 下圖是WebGL渲染管線的示意圖: Vertex Buffer Objects (VBOs) VBOS中包含了用于描述幾何體的信息。如,幾何體的頂點坐標,法線坐標,顏色,紋理坐標等。 Index Buffer Objects (IBOs) IBOs中包含了描述頂點關系的信…

C#的多線程UI窗體控件顯示方案 - 開源研究系列文章

上次編寫了《LUAgent服務器端工具》這個應用,然后里面需要新啟動一個線程去對文件進行上傳到FTP服務器,但是新線程里無法對應用主線程UI的內容進行更改,所以就需要在線程里設置主UI線程里控件信息的方法,于是就有了此博文。此文記…

Rocky Linux 9 快速安裝docker 教程

前述 CentOS 7系統將于2024年06月30日停止維護服務。CentOS官方不再提供CentOS 及后續版本,不再支持新的軟件和補丁更新。CentOS用戶現有業務隨時面臨宕機和安全風險,并無法確保及時恢復。由于 CentOS Stream 相對不穩定,剛好在尋找平替系統…

idm 支持斷點續傳嗎 idm 斷點續傳如何使用 idm斷點續傳怎么解決 idm下載中斷后無法繼續下載

斷點續傳功能,讓我再也不會懼怕下載大型文件。在斷點續傳的幫助下,用戶可以隨時暫停下載任務,并在空閑時繼續之前的下載進程。下載文件不懼網絡波動,斷點續傳讓下載過程更穩定。有關 idm 支持斷點續傳嗎,idm 斷點續傳如…

JavaScript:if-else類型

目錄 任務描述 相關知識 if語句 if-else語句 匹配問題 編程要求 任務描述 本關任務:根據成績判斷考試結果。 相關知識 在編程中,我們常常根據變量是否滿足某個條件來執行不同的語句。 JavaScript中利用以if關鍵字開頭的條件語句達到以上目的&am…

商城項目回顧

哈哈,準備期末考試去了,項目停了一段時間。現在又忘的差不多了。所以專門寫一篇博客總結前期項目的知識點。 Client軟件包 代碼加總結: 這段代碼實現了一個簡單的客戶端程序,用于與服務器建立連接、發送登錄信息并接收服務器的響…

筆記:tencentos2.4升級gcc4到gcc8.5

由于開發需要將tencentos2.4的GCC版本升級到和cat /proc/version中GCC8.4較接近的版本。 過程如下: 首先 ls -al /etc/yum.repos.d/ 觀察tlinux.repo 可以看到類似: [tlinux] nametlinux-$releasever - tlinux baseurlhttp://mirrors.tencent.com/t…

在主線程和非主線程調用 DispatchQueue.main.sync { }

在 Swift 中,DispatchQueue.main.sync { } 的行為取決于當前執行代碼的線程。以下是詳細的說明: 主線程調用 DispatchQueue.main.sync { } 當在主線程上調用 DispatchQueue.main.sync { } 時,會發生死鎖(Deadlock)。…

|從零搭建網絡| VisionTransformer網絡詳解及搭建

🌜|從零搭建網絡| VisionTransformer系列網絡詳解及搭建🌛 文章目錄 🌜|從零搭建網絡| VisionTransformer系列網絡詳解及搭建🌛🌜 前言 🌛🌜 VIT模型詳解 🌛🌜 VIT模型架…

【Perl CGI腳本全解析】打造動態Web應用的秘籍

標題:【Perl CGI腳本全解析】打造動態Web應用的秘籍 在Web開發的早期,Perl因其強大的文本處理能力和易于編寫的CGI腳本而成為開發動態網站的熱門選擇。盡管現代Web開發已經涌現了許多新的技術和框架,但Perl CGI腳本依然在某些場景下發揮著作…

計算機相關專業入門

IT專業入門,高考假期預習指南 七月來臨,各省高考分數已揭榜完成。而高考的完結并不意味著學習的結束,而是新旅程的開始。對于有志于踏入IT領域的各位小伙伴,這個假期是開啟探索IT世界的絕佳時機。作為該領域的前行者,…

mybatis、mybatis-plus插件開發,實現數據脫敏功能

首先說一下mybatis中四大組件的作用,下面開發的插件攔截器會使用 四大組件Executor、StatementHandler、ParameterHandler、ResultSetHandler Executor: Executor 是 MyBatis 中的執行器,負責 SQL 語句的執行工作。它通過調度 StatementHan…

python基礎語法 004-3流程控制- while

1 while while 主要用的場景沒有 for 循環多。 while循環&#xff1a;主要運行場景 我不知道什么時候結束。。。不知道運行多少次 1.1 基本用法 # while 4 > 3: #一直執行 # print("hell0")while 4 < 3: #不會打印&#xff0c;什么都沒有print("…

IT之旅啟航:高考后IT專業預習全攻略

?作者主頁&#xff1a; Mr.Zwq??個人簡介&#xff1a;一個正在努力學技術的Python領域創作者&#xff0c;擅長爬蟲&#xff0c;逆向&#xff0c;全棧方向&#xff0c;專注基礎和實戰分享&#xff0c;歡迎咨詢&#xff01; 您的點贊、關注、收藏、評論&#xff0c;是對我最大…

opencv 處理圖像去噪的幾種方法

OpenCV 提供了多種圖像去噪的方法&#xff0c;以下是一些常見的去噪技術以及相應的 Python 代碼示例&#xff1a; 均值濾波&#xff1a;使用像素鄰域的灰度均值代替該像素的值。 import cv2 import numpy as np import matplotlib.pyplot as pltimg cv2.imread("4.jpg&qu…