摘要:原理查看所有文檔頁面前端開發(fā)文檔,獲取更多信息。初始化階段事件名解釋初始化參數(shù)從配置文件和語句中讀取與合并參數(shù),得出最終的參數(shù)。以上處理的相關(guān)配置如下編寫編寫的職責(zé)由上面的例子可以看出一個的職責(zé)是單一的,只需要完成一種轉(zhuǎn)換。
webpack原理
查看所有文檔頁面:前端開發(fā)文檔,獲取更多信息。工作原理概括 基本概念
原文鏈接:webpack原理,原文廣告模態(tài)框遮擋,閱讀體驗(yàn)不好,所以整理成本文,方便查找。
在了解 Webpack 原理前,需要掌握以下幾個核心概念,以方便后面的理解:
Entry:入口,Webpack 執(zhí)行構(gòu)建的第一步將從 Entry 開始,可抽象成輸入。
Module:模塊,在 Webpack 里一切皆模塊,一個模塊對應(yīng)著一個文件。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊。
Chunk:代碼塊,一個 Chunk 由多個模塊組合而成,用于代碼合并與分割。
Loader:模塊轉(zhuǎn)換器,用于把模塊原內(nèi)容按照需求轉(zhuǎn)換成新內(nèi)容。
Plugin:擴(kuò)展插件,在 Webpack 構(gòu)建流程中的特定時機(jī)會廣播出對應(yīng)的事件,插件可以監(jiān)聽這些事件的發(fā)生,在特定時機(jī)做對應(yīng)的事情。
流程概括Webpack 的運(yùn)行流程是一個串行的過程,從啟動到結(jié)束會依次執(zhí)行以下流程:
初始化參數(shù):從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù);
開始編譯:用上一步得到的參數(shù)初始化 Compiler 對象,加載所有配置的插件,執(zhí)行對象的 run 方法開始執(zhí)行編譯;
確定入口:根據(jù)配置中的 entry 找出所有的入口文件;
編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對模塊進(jìn)行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理;
完成模塊編譯:在經(jīng)過第4步使用 Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系;
輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉(zhuǎn)換成一個多帶帶的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會;
輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)。
在以上過程中,Webpack 會在特定的時間點(diǎn)廣播出特定的事件,插件在監(jiān)聽到感興趣的事件后會執(zhí)行特定的邏輯,并且插件可以調(diào)用 Webpack 提供的 API 改變 Webpack 的運(yùn)行結(jié)果。
流程細(xì)節(jié)Webpack 的構(gòu)建流程可以分為以下三大階段:
初始化:啟動構(gòu)建,讀取與合并配置參數(shù),加載 Plugin,實(shí)例化 Compiler。
編譯:從 Entry 發(fā)出,針對每個 Module 串行調(diào)用對應(yīng)的 Loader 去翻譯文件內(nèi)容,再找到該 Module 依賴的 Module,遞歸地進(jìn)行編譯處理。
輸出:對編譯后的 Module 組合成 Chunk,把 Chunk 轉(zhuǎn)換成文件,輸出到文件系統(tǒng)。
如果只執(zhí)行一次構(gòu)建,以上階段將會按照順序各執(zhí)行一次。但在開啟監(jiān)聽模式下,流程將變?yōu)槿缦拢?/p>
在每個大階段中又會發(fā)生很多事件,Webpack 會把這些事件廣播出來供給 Plugin 使用,下面來一一介紹。
初始化階段事件名 | 解釋 |
---|---|
初始化參數(shù) | 從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù)。 這個過程中還會執(zhí)行配置文件中的插件實(shí)例化語句 new Plugin()。 |
實(shí)例化 Compiler | 用上一步得到的參數(shù)初始化 Compiler 實(shí)例,Compiler 負(fù)責(zé)文件監(jiān)聽和啟動編譯。Compiler 實(shí)例中包含了完整的 Webpack 配置,全局只有一個 Compiler 實(shí)例。 |
加載插件 | 依次調(diào)用插件的 apply 方法,讓插件可以監(jiān)聽后續(xù)的所有事件節(jié)點(diǎn)。同時給插件傳入 compiler 實(shí)例的引用,以方便插件通過 compiler 調(diào)用 Webpack 提供的 API。 |
environment | 開始應(yīng)用 Node.js 風(fēng)格的文件系統(tǒng)到 compiler 對象,以方便后續(xù)的文件尋找和讀取。 |
entry-option | 讀取配置的 Entrys,為每個 Entry 實(shí)例化一個對應(yīng)的 EntryPlugin,為后面該 Entry 的遞歸解析工作做準(zhǔn)備。 |
after-plugins | 調(diào)用完所有內(nèi)置的和配置的插件的 apply 方法。 |
after-resolvers | 根據(jù)配置初始化完 resolver,resolver 負(fù)責(zé)在文件系統(tǒng)中尋找指定路徑的文件。 |
空格 | 空格 |
空格 | 空格 |
空格 | 空格 |
事件名 | 解釋 |
---|---|
run | 啟動一次新的編譯。 |
watch-run | 和 run 類似,區(qū)別在于它是在監(jiān)聽模式下啟動的編譯,在這個事件中可以獲取到是哪些文件發(fā)生了變化導(dǎo)致重新啟動一次新的編譯。 |
compile | 該事件是為了告訴插件一次新的編譯將要啟動,同時會給插件帶上 compiler 對象。 |
compilation | 當(dāng) Webpack 以開發(fā)模式運(yùn)行時,每當(dāng)檢測到文件變化,一次新的 Compilation 將被創(chuàng)建。一個 Compilation 對象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。Compilation 對象也提供了很多事件回調(diào)供插件做擴(kuò)展。 |
make | 一個新的 Compilation 創(chuàng)建完畢,即將從 Entry 開始讀取文件,根據(jù)文件類型和配置的 Loader 對文件進(jìn)行編譯,編譯完后再找出該文件依賴的文件,遞歸的編譯和解析。 |
after-compile | 一次 Compilation 執(zhí)行完成。 |
invalid | 當(dāng)遇到文件不存在、文件編譯錯誤等異常時會觸發(fā)該事件,該事件不會導(dǎo)致 Webpack 退出。 |
空格 | 空格 |
空格 | 空格 |
在編譯階段中,最重要的要數(shù) compilation 事件了,因?yàn)樵?compilation 階段調(diào)用了 Loader 完成了每個模塊的轉(zhuǎn)換操作,在 compilation 階段又包括很多小的事件,它們分別是:
事件名 | 解釋 |
---|---|
build-module | 使用對應(yīng)的 Loader 去轉(zhuǎn)換一個模塊。 |
normal-module-loader | 在用 Loader 對一個模塊轉(zhuǎn)換完后,使用 acorn 解析轉(zhuǎn)換后的內(nèi)容,輸出對應(yīng)的抽象語法樹(AST),以方便 Webpack 后面對代碼的分析。 |
program | 從配置的入口模塊開始,分析其 AST,當(dāng)遇到 require 等導(dǎo)入其它模塊語句時,便將其加入到依賴的模塊列表,同時對新找出的依賴模塊遞歸分析,最終搞清所有模塊的依賴關(guān)系。 |
seal | 所有模塊及其依賴的模塊都通過 Loader 轉(zhuǎn)換完成后,根據(jù)依賴關(guān)系開始生成 Chunk。 |
事件名 | 解釋 |
---|---|
should-emit | 所有需要輸出的文件已經(jīng)生成好,詢問插件哪些文件需要輸出,哪些不需要。 |
emit | 確定好要輸出哪些文件后,執(zhí)行文件輸出,可以在這里獲取和修改輸出內(nèi)容。 |
after-emit | 文件輸出完畢。 |
done | 成功完成一次完成的編譯和輸出流程。 |
failed | 如果在編譯和輸出流程中遇到異常導(dǎo)致 Webpack 退出時,就會直接跳轉(zhuǎn)到本步驟,插件可以在本事件中獲取到具體的錯誤原因。 |
在輸出階段已經(jīng)得到了各個模塊經(jīng)過轉(zhuǎn)換后的結(jié)果和其依賴關(guān)系,并且把相關(guān)模塊組合在一起形成一個個 Chunk。 在輸出階段會根據(jù) Chunk 的類型,使用對應(yīng)的模版生成最終要要輸出的文件內(nèi)容。
輸出文件分析雖然在前面的章節(jié)中你學(xué)會了如何使用 Webpack ,也大致知道其工作原理,可是你想過 Webpack 輸出的 bundle.js 是什么樣子的嗎? 為什么原來一個個的模塊文件被合并成了一個多帶帶的文件?為什么 bundle.js 能直接運(yùn)行在瀏覽器中? 本節(jié)將解釋清楚以上問題。
先來看看由 安裝與使用 中最簡單的項(xiàng)目構(gòu)建出的 bundle.js 文件內(nèi)容,代碼如下:
See the Pen bundle.js by whjin (@whjin) on CodePen.
這里的 bundle.js 和上面所講的 bundle.js 非常相似,區(qū)別在于:
多了一個 __webpack_require__.e 用于加載被分割出去的,需要異步加載的 Chunk 對應(yīng)的文件;
多了一個 webpackJsonp 函數(shù)用于從異步加載的文件中安裝模塊。
在使用了 CommonsChunkPlugin 去提取公共代碼時輸出的文件和使用了異步加載時輸出的文件是一樣的,都會有 __webpack_require__.e 和 webpackJsonp。 原因在于提取公共代碼和異步加載本質(zhì)上都是代碼分割。
編寫 LoaderLoader 就像是一個翻譯員,能把源文件經(jīng)過轉(zhuǎn)化后輸出新的結(jié)果,并且一個文件還可以鏈?zhǔn)降慕?jīng)過多個翻譯員翻譯。
以處理 SCSS 文件為例:
SCSS 源代碼會先交給 sass-loader 把 SCSS 轉(zhuǎn)換成 CSS;
把 sass-loader 輸出的 CSS 交給 css-loader 處理,找出 CSS 中依賴的資源、壓縮 CSS 等;
把 css-loader 輸出的 CSS 交給 style-loader 處理,轉(zhuǎn)換成通過腳本加載的 JavaScript 代碼;
可以看出以上的處理過程需要有順序的鏈?zhǔn)綀?zhí)行,先 sass-loader 再 css-loader 再 style-loader。 以上處理的 Webpack 相關(guān)配置如下:
See the Pen 編寫 Loader by whjin (@whjin) on CodePen.
Webpack 會從配置的入口模塊出發(fā),依次找出所有的依賴模塊,當(dāng)入口模塊或者其依賴的模塊發(fā)生變化時, 就會觸發(fā)一次新的 Compilation。
在開發(fā)插件時經(jīng)常需要知道是哪個文件發(fā)生變化導(dǎo)致了新的 Compilation,為此可以使用如下代碼:
See the Pen Compilation by whjin (@whjin) on CodePen.