你好,我是勾勾,今天分享的内容依然來自我司前端團隊。主題是如何使用 Webpack 實作子產品化打包。
相信通過上篇文章,你應該對前端子產品化有了更完整的認識。在文章結尾我們提出了對子產品化打包方案或工具的設想或者說是訴求:
- 能夠将散落的子產品打包到一起;
- 能夠編譯代碼中的新特性;
- 能夠支援不同種類的前端資源子產品。

目前,前端領域有一些工具能夠很好的滿足以上這 3 個需求,其中最為主流的就是 Webpack、Parcel 和 Rollup,我們以 Webpack 為例:
Webpack 作為一個子產品打包工具,本身就可以解決子產品化代碼打包的問題,将零散的 JavaScript 代碼打包到一個 JS 檔案中。
對于有環境相容問題的代碼,Webpack 可以在打包過程中通過 Loader 機制對其實作編譯轉換,然後再進行打包。
對于不同類型的前端子產品類型,Webpack 支援在 JavaScript 中以子產品化的方式載入任意類型的資源檔案,例如,我們可以通過 Webpack 實作在 JavaScript 中加載 CSS 檔案,被加載的 CSS 檔案将會通過 style 标簽的方式工作。
除此之外,Webpack 還具備代碼拆分的能力,它能夠将應用中所有的子產品按照我們的需要分塊打包。這樣一來,就不用擔心全部代碼打包到一起,産生單個檔案過大,導緻加載慢的問題。我們可以把應用初次加載所必需的子產品打包到一起,其他的子產品再單獨打包,等到應用工作過程中實際需要用到某個子產品,再異步加載該子產品,實作增量加載,或者叫作漸進式加載,非常适合現代化的大型 Web 應用。
當然,除了 Webpack,其他的打包工具也都類似,總之,所有的打包工具都是以實作子產品化為目标,讓我們可以在開發階段更好的享受子產品化帶來的優勢,同時又不必擔心子產品化在生産環境中産生新的問題。
Webpack 快速上手
Webpack 作為目前最主流的前端子產品打包器,提供了一整套前端項目子產品化方案,而不僅僅局限于對 JavaScript 的子產品化。通過 Webpack,我們可以輕松的對前端項目開發過程中涉及的所有資源進行子產品化。
因為 Webpack 的設計思想比較先進,起初的使用過程比較煩瑣,再加上文檔也晦澀難懂,是以在最開始的時候,Webpack 對開發者并不友好,但是随着版本的疊代,官方文檔的不斷更新,目前 Webpack 對開發者已經非常友好了。此外,随着 React 和 這類架構的普及,Webpack 也随之受到了越來越多的關注,現階段可以覆寫絕大多數現代 Web 應用的開發過程。
接下來我将通過一個案例,帶你快速了解 Webpack 的基本使用,具體操作如下所示:
在上面這個案例中,我們建立了兩個 JS 檔案,其中 中以 ES Modules 的方式導出了一個建立元素的函數,然後在 中導入 并使用了這個子產品,最後在 html 檔案中通過 script 标簽,以子產品化的方式引入了 。
按照 ES Modules 的标準,這裡的 可以直接在浏覽器中正常工作,但是對于不支援 ES Modules 标準的浏覽器,直接使用就會出現錯誤,是以我們需要使用 Webpack 這樣的工具,将我們這裡按照子產品化方式拆分的 JS 代碼再次打包到一起。
接下來我們就嘗試引入 Webpack 去處理上述案例中的 JS 子產品打包。由于 Webpack 是一個 npm 工具子產品,是以我們先初始化一個 檔案,用來管理 npm 依賴版本,完成之後,再來安裝 Webpack 的核心子產品以及它的 CLI 子產品,具體操作如下:
$ npm init --yes
$ npm i webpack webpack-cli --save-dev
P.S. webpack 是 Webpack 的核心子產品,webpack-cli 是 Webpack 的 CLI 程式,用來在指令行中調用 Webpack。
安裝完成之後,webpack-cli 所提供的 CLI 程式就會出現在 node_modules/.bin 目錄當中,我們可以通過 npx 快速找到 CLI 并運作它,具體操作如下:
$ npx webpack --version
P.S. npx 是 npm 5.2 以後新增的一個指令,可以用來更友善的執行遠端子產品或者項目 node_modules 中的 CLI 程式。
這裡我們使用的 Webpack 版本是 ,有了 Webpack 後,就可以直接運作 webpack 指令來打包 JS 子產品代碼,具體操作如下:
$ npx webpack
這個指令在執行的過程中,Webpack 會自動從 src/ 檔案開始打包,然後根據代碼中的子產品導入操作,自動将所有用到的子產品代碼打包到一起。
完成之後,控制台會提示:順着 有兩個 JS 檔案被打包到了一起。與之對應的就是項目的根目錄下多出了一個 dist 目錄,我們的打包結果就存放在這個目錄下的 檔案中,具體操作如下圖所示:
這裡我們回到 中修改引入檔案的路徑,由于打包後的代碼就不會再有 import 和 export 了,是以我們可以删除 type="module"。再次回到浏覽器中,檢視這個頁面,這時我們的代碼仍然可以正常工作, 的代碼如下所示:
我們也可以将 Webpack 指令定義到 npm scripts 中,這樣每次使用起來會更加友善,具體如下:
對于 Webpack 最基本的使用,總結下來就是:先安裝 webpack 相關的 npm 包,然後使用 webpack-cli 所提供的指令行工具進行打包。
配置 Webpack 的打包過程
Webpack 4 以後的版本支援零配置的方式直接啟動打包,整個過程會按照約定将 src/ 作為打包入口,最終打包的結果會存放到 dist/ 中。
但很多時候我們需要自定義這些路徑約定,例如,在下面這個案例中,我需要它的打包入口是 src/,那此時我們通過配置檔案的方式修改 Webpack 的預設配置,在項目的根目錄下添加一個 ,具體結構如下:
是一個運作在 環境中的 JS 檔案,也就是說我們需要按照 CommonJS 的方式編寫代碼,這個檔案可以導出一個對象,我們可以通過所導出對象的屬性完成相應的配置選項。
這裡先嘗試添加一個 entry 屬性,這個屬性的作用就是指定 Webpack 打包的入口檔案路徑。我們将其設定為 src/,具體代碼如下所示:
配置完成之後,回到指令行終端重新運作打包指令,此時 Webpack 就會從 src/ 檔案開始打包。
除了 entry 的配置以外,我們還可以通過 output 屬性設定輸出檔案的位置。output 屬性的值必須是一個對象,通過這個對象的 filename 指定輸出檔案的檔案名稱,path 指定輸出的目錄,具體代碼如下所示:
TIPS: 是運作在 環境中的代碼,是以直接可以使用 path 之類的 内置子產品。
由于 Webpack 支援的配置有很多,篇幅的關系,這裡我們就不一一介紹了,詳細的文檔你可以在 Webpack 的官網中找到:
讓配置檔案支援智能提示
在這裡,我想跟你分享我在編寫 Webpack 配置檔案時用過的一個小技巧,因為 Webpack 的配置項比較多,而且很多選項都支援不同類型的配置方式。如果你剛剛接觸 Webpack 的配置,這些配置選項一定會讓你感到頭大。如果開發工具能夠為 Webpack 配置檔案提供智能提示的話,這種痛苦就會減小很多,配置起來,效率和準确度也會大大提高。
我們知道, VSCode 對于代碼的自動提示是根據成員的類型推斷出來的,換句話說,如果 VSCode 知道目前變量的類型,就可以給出正确的智能提示。即便你沒有使用 TypeScript 這種類型友好的語言,也可以通過類型注釋的方式去标注變量的類型。
預設 VSCode 并不知道 Webpack 配置對象的類型,我們通過 import 的方式導入 Webpack 子產品中的 Configuration 類型,然後根據類型注釋的方式将變量标注為這個類型,這樣我們在編寫這個對象的内部結構時就可以有正确的智能提示了,具體代碼如下所示:
需要注意的是:我們添加的 import 語句隻是為了導入 Webpack 配置對象的類型,這樣做的目的是為了标注 config 對象的類型,進而實作智能提示。在配置完成後一定要記得注釋掉這段輔助代碼,因為在 環境中預設還不支援 import 語句,如果執行這段代碼會出現錯誤。
沒有智能提示的效果,如下所示:
加上類型标注實作智能提示的效果,如下所示:
使用 import 語句導入 Configuration 類型的方式固然好了解,但是在不同的環境中還是會有各種各樣的問題,例如我們這裡在 環境中,就必須要額外注釋掉這個導入類型的語句,才能正常工作。
是以我一般的做法是直接在類型注釋中使用 import 動态導入類型,具體代碼如下:
這種方式同樣也可以實作載入類型,而且相比于在代碼中通過 import 語句導入類型更為友善,也更為合理。
不過需要注意一點,這種導入類型的方式并不是 ES Modules 中的 Dynamic Imports,而是 TypeScript 中提供特性。雖然我們這裡隻是一個 JavaScript 檔案,但是在 VSCode 中的類型系統都是基于 TypeScript 的,是以可以直接按照這種方式使用,詳細資訊你可以參考這種 import-types 的文檔。
連結:
其次,這種 @type 類型注釋的方式是基于 JSDoc 實作的。JSDoc 中類型注釋的用法還有很多,詳細可以參考官方文檔中對 @type 标簽的介紹。
連結:
Webpack 工作模式
Webpack 4 新增了一個工作模式的用法,這種用法大大簡化了 Webpack 配置的複雜程度。你可以把它了解為針對不同環境的幾組預設配置:
- production 模式下,啟動内置優化插件,自動優化打包結果,打包速度偏慢;
- development 模式下,自動優化打包速度,添加一些調試過程中的輔助插件;
- none 模式下,運作最原始的打包,不做任何額外處理。
針對工作模式的選項,如果你沒有配置一個明确的值,打包過程中指令行終端會列印一個對應的配置警告。在這種情況下 Webpack 将預設使用 production 模式去工作。
production 模式下 Webpack 内部會自動啟動一些優化插件,例如,自動壓縮打包後的代碼。這對實際生産環境是非常友好的,但是打包的結果就無法閱讀了。
修改 Webpack 工作模式的方式有兩種:
- 通過 CLI --mode 參數傳入;
- 通過配置檔案設定 mode 屬性。
上述三種 Webpack 工作模式的詳細差異我們不再贅述了,你可以在官方文檔中檢視:
打包結果運作原理
最後,我們來一起解讀 Webpack 打包後生成的 檔案,深入了解 Webpack 是如何把這些子產品合并到一起,而且還能正常工作的。
為了更好的了解打包後的代碼,我們先将 Webpack 工作模式設定為 none,這樣 Webpack 就會按照最原始的狀态進行打包,所得到的結果更容易了解和閱讀。
按照 none 模式打包完成後,我們打開最終生成的 檔案,如下圖所示:
我們可以先把代碼全部折疊起來,以便于了解整體的結構,如下圖所示:
TIPS:-VSCode 中折疊代碼的快捷鍵是 Ctrl + K,Ctrl + 0 (macOS:Command + K,Command + 0)
整體生成的代碼其實就是一個立即執行函數,這個函數是 Webpack 工作入口(webpackBootstrap),它接收一個 modules 參數,調用時傳入了一個數組。
展開這個數組,裡面的元素均是參數清單相同的函數。這裡的函數對應的就是我們源代碼中的子產品,也就是說每個子產品最終被包裹到了這樣一個函數中,進而實作子產品私有作用域,如下圖所示:
我們再來展開 Webpack 工作入口函數,如下圖所示:
這個函數内部并不複雜,而且注釋也很清晰,最開始定義了一個 installedModules 對象用于存放或者緩存加載過的子產品。緊接着定義了一個 require 函數,顧名思義,這個函數是用來加載子產品的。再往後就是在 require 函數上挂載了一些其他的資料和工具函數,這些暫時不用關心。
這個函數執行到最後調用了 require 函數,傳入的子產品 id 為 0,開始加載子產品。子產品 id 實際上就是子產品數組的元素下标,也就是說這裡開始加載源代碼中所謂的入口子產品,如下圖所示:
為了更好的了解 的執行過程,你可以把它運作到浏覽器中,然後通過 Chrome 的 Devtools 單步調試一下。調試過程我單獨錄制了一個視訊,如下所示:
寫在最後
整體上對于 Webpack 的基本使用其實并不複雜,特别是在 Webpack 4 以後,很多配置都已經被簡化了,在這種配置并不複雜的前提下,開發人員對它的掌握程度主要就展現在了是否能夠了解它的工作機制和原理上了。
就拿 Webpack 打包過後的結果來說,大多數的開發者其實根本不會關心它内部的結構是怎樣的,又是如何運作起來的,總覺得不需要關心,但是當這種“不用關心”的事情越積越多,整個開發過程不可控的點也會随之增多,當出現問題時,也就很難定位問題的根源了。
其實通過我們的探索你會發現,當你打開“黑盒子”後,裡面的東西并沒有想象的那麼複雜。很多時候你離“成功”就隻有一步之遙,而驅使你走向“成功”的其實是你的好奇心。在我看來,好奇心應該是一個優秀開發者的基本素質,對待未知的好奇就是我們進步的源泉,與君共勉。
最後來總結一下今天文章的重點,你也可以通過這幾個重點檢視一下自己了解的程度:
1. Webpack 是如何滿足子產品化打包需求的。
2. Webpack 打包的配置方式以及一個可以實作配置檔案智能提示的小技巧。
3. Webpack 工作模式特性的作用。
4. 通過 Webpack 打包後的結果是如何運作起來的?
本文轉載自公号“勾勾的前端世界”