Webpack 5 正式發布至今已近 2 個月。由於贊助狀況,我們無法像我們希望的那樣投入大量時間在 Webpack 上。僅代表我個人 (@sokra) 發言,我享受這段小小的休息時間,並著手進行一些額外的專案。具有諷刺意味的是,在我使用 Webpack 5 及其所有尖端功能(資產模組、工作人員支援、持續快取)時,我發現了 Webpack 5 中更多錯誤,人們在將專案升級到 Webpack 5 時可能會遇到這些錯誤,這導致了大量錯誤修正工作。以下是簡要摘要
Webpack 中已公開更多內容,包括類型化和執行時間。已進行一些低處理效能改善。在某些情況下,沒有分號的程式碼會產生一些無效/不正確的程式碼,此問題已獲得修正。無副作用程式碼 + 串接模組 + 重新匯出的組合導致一些極端情況,這些情況已獲得修正(至少已知的那些)。
但一位使用者回報的一個錯誤導致需要一個全新的內部功能。如果您覺得 Webpack 內部很無聊或太複雜,您可以略過它並前往下一章。
若要觸發錯誤,我們需要三個要素
production
模式中的最佳化會對每個執行階段 (通常與進入點相同) 執行已使用的匯出分析 (Tree Shaking),這表示 webpack 可以個別最佳化每個執行階段 (或進入點)。optimization.splitChunks
設定允許強制將模組合併成單一區塊。這是透過傳遞 name
選項來完成的。例如,{ test: /node_modules/, name: "vendors" }
會將 node_modules
中的模組合併成單一區塊。儘管這通常不建議使用,但在某些情況下是可行的,而且可能很有意義。整件事都是關於權衡取捨,而選擇將所有廠商合併成單一區塊對於重複造訪或多個進入點之間的長期快取來說可能是有益的。import
陳述式根本不會產生執行階段程式碼。在一個邊緣情況中會發生問題,其中來自兩個進入點的模組會合併成單一區塊,而且它們會參照一個副作用模組,而這個副作用模組不在共用區塊中,因為只有一個進入點使用副作用模組的匯出。共用區塊中的模組會被兩個進入點使用,因此它們需要包含任何進入點使用的匯出。這表示它會產生參照副作用模組的程式碼,而這個副作用模組在上述邊緣情況中對於另一個進入點在執行階段不可用,這會導致執行階段出現 undefined is not a function
或 cannot read property 'call' of undefined
錯誤。
一個潛在的修正方法是將副作用模組包含在所有進入點中,但由於這個模組並非真的需要,這會浪費套件大小。因此我們採取了另一種途徑,這需要開發一個新功能:執行階段相依程式碼產生
。這允許產生會根據執行的執行階段而有不同行為的程式碼。
換句話說,我們將一些產生的程式碼包在 if
區塊中,因此它們只在一個執行階段執行。在此範例中,這會影響參照無副作用模組的 import
陳述式。匯入只會針對其中一個進入點執行。這可避免包含不必要的模組,而且即使可用,也能避免執行不必要的程式碼。因此,即使您將所有程式碼合併成一個區塊,只會執行真正使用的程式碼。
到目前為止的說明,希望不會太無聊...
假設我們可以解決贊助狀況,以下是 2021 年的計畫
我們的首要任務是穩定 webpack 5。到目前為止,情況看起來還不錯。最近回報的大部分關鍵錯誤影響一些臨界狀況。因此,我猜 webpack 5 應該適用於一般狀況。但是,處理臨界狀況是(而且應該持續)webpack 的優點之一,因此我們希望繼續努力修正這些狀況。我們認為許多 webpack 使用者需要自訂項目來建置,而這是 webpack 透過可組態性和豐富的附加元件系統提供的功能。
EcmaScript 模組 (ESM) 逐漸獲得廣泛採用。在編寫方面,它們已經成為撰寫程式碼的實際標準。在瀏覽器支援方面,看起來也不錯(IE11 和一些較舊的手機瀏覽器除外)。瀏覽器在支援 WebWorkers 的 ESM 方面仍然有點不足。
也可以產生在 type=module
程式碼標籤中執行的套件,但目前這樣做的好處很少。
webpack 中有多個領域可以改善 ESM 支援
在鎖定網頁時,webpack 會透過 script
標籤載入區塊。在鎖定 node.js 時,webpack 會透過 require
或 fs
+ vm
載入區塊。在鎖定 WebWorkers 時,webpack 會透過 importScripts
載入區塊。
在不久的將來,所有這些環境都支援 ESM,更重要的是動態 import()
函式。因此,基於 import()
的區塊載入機制可以統一所有這些環境,甚至需要更少的執行時期程式碼。
目前,webpack 中依需求載入的區塊永遠都是模組的容器,而且從不直接執行模組程式碼。在模組中撰寫 import("./module")
時,這將編譯成類似 __webpack_load_chunk__("chunk-containing-module.js").then(() => __webpack_require__("./module"))
的內容。在許多情況下,這無法變更(例如,在載入多個區塊或載入 CSS 時),但在某些情況下,webpack 可以產生直接執行所包含模組的區塊。這可能會產生較少的程式碼,而且可以避免在區塊中進行函式封裝。
目前,我還不確定這是否值得,但至少值得研究一下。
目前無法透過 output.library.type: "module"
為套件產生 ESM 匯出。這在將 webpack 套件整合到 ESM 載入環境或內嵌腳本時很有用。
Webpack 允許定義 externals
,這些模組未套件化,但會在執行階段存在。有許多類型的外部程式碼,從全域變數到 CommonJs/AMD/System,再到從傳統腳本標籤載入。甚至 import()
(type: "import"
) 可用於載入外部程式碼,但 import
(type: "module"
) 尚未可用。
有趣的是,即使 type: "module"
尚未支援,webpack 在撰寫 import x from "https://example.com/module.js"
等時,已經將其用作預設值。預設值已選擇無縫新增對 ESM 外部程式碼的支援,而不會引入重大變更。
import
中的絕對 URL 可能有意義,例如在使用提供其 API 作為 ESM 的外部服務時:import { event } from "https://analytics.company.com/api/v1.js"
(import("https://analytics.company.com/api/v1.js")
可能更有意義,以便在依賴此外部服務時優雅地處理錯誤,但錯誤也可能在模組圖中較高層級被偵測到)。
與往常一樣,externals
設定允許將任何模組名稱對應到外部程式碼
export default {
externalsType: 'module',
externals: {
analytics: 'https://analytics.company.com/api/v1.js',
svelte: 'https://jspm.dev/svelte@3',
react: 'https://cdn.skypack.dev/preact@10',
'react-dom': 'https://esm.sh/[react,react-dom]/react-dom',
},
};
當支援 ESM 匯出和匯入時,人們可能會認為套件化函式庫是有意義的,在某些情況下這可能是真的,但在許多情況下,原生套件化會導致更糟的結果。最大的問題是 "sideEffects": false
標記。它會影響每個檔案基礎上的模組,以略過整個模組。當串接多個沒有副作用的模組時,就不再可能略過個別模組,這會導致在未套用函式庫所有匯出時,最佳化變差。
如果輸出應該是一個稍後會由打包器處理的函式庫,則需要考慮這一點。
我可以想到一種特殊模式,它不套用分塊,而是透過 ESM 輸入和輸出(或 CommonJS require
)發出原始(已處理)模組。這表示載入器、模組圖和資源最佳化會執行,但不會建立分塊圖,且模組圖中的每個模組會發出為一個獨立檔案。
在產生 ESM 捆綁時,所有包含的程式碼都會強制進入嚴格模式。對許多模組來說,這不是問題,但有一些較舊的套件可能會對不同的語意有問題。我們希望對這些情況顯示警告。
Webpack 4 和 5 為支援非 JS 模組類型做了很多工作,而且 webpack 5 預設已支援一些模組類型:JS(ESM/CJS/AMD)、JSON、WebAssembly、資源。由於 webpack 5,我們的長期目標之一是成為網路應用程式最佳化器,目標是支援瀏覽器支援的所有內容。因此,技術上來說,純粹的網路應用程式應該可以與 webpack 搭配使用,但會在執行中進行最佳化。
Webpack 5 的初始版本已朝這個方向邁出一些重大步驟:new Worker
本機支援。new URL(...)
本機支援(資源)。
WebAssembly 和 JSON 已獲得支援,即使提案尚未完成。
但完整的說明仍缺少兩種資源類型:HTML 和 CSS。
目前 webpack 支援 CSS 的方式是透過 css-loader
、style-loader
或 mini-css-extract-plugin
。這種方式運作得相當良好,但我認為如果能將 CSS 支援為 webpack 中的原生模組類型,我們可以做得更多。
一個主要的優點是開發人員體驗:mini-css-extract-plugin
設定並不容易,而移除它將能大幅簡化開發人員的工作。這並不表示你無法在它上面加入額外的自訂設定。我看到許多開發人員並未使用原始 CSS,而是使用預處理器 (如果原生支援 CSS,將會像這樣:{ test: /\.sass$/, type: "stylesheet", use: "sass-loader" }
)。
根據 2020 年 CSS 狀態,CSS 模組是撰寫模組化 CSS 的一種熱門方式,而將其作為 webpack 中的原生模組類型,可以從模組圖最佳化中受益,例如 Tree Shaking (已使用的匯出最佳化和副作用最佳化)。使用 CSS 模組時,這表示產生的 CSS 將只包含從應用程式參照的 CSS 規則 (正如人們習慣於 JS Tree Shaking)。
有些潛在的 CSS 模組特定最佳化,可以使用 webpack 對應用程式的整體認識來達成:可以將 CSS 規則分割成較小的規則,以避免重複常見屬性。這可能會產生較小的負載,因為輸出的 CSS 包含較少的重複屬性 (原子 CSS)。
但這裡有一個很大的「但是」:WebComponents 社群正致力於一個不同的「CSS 模組」提案,計畫由瀏覽器原生支援。至少這是提案的目標。遺憾的是,這個提案與目前前端生態系統中使用的提案不同,但使用類似的語法。通常,webpack 會與提案保持一致,因此這是這裡需要考量的因素。我們必須檢查是否有可能避免潛在的衝突。
遵循 Parcels 範例,我們也希望原生支援 HTML 作為進入點。支援這項功能符合身為 Web 應用程式最佳化器的目標,因為 Web 應用程式通常以 HTML 開始。這對於初學者來說也是大幅改善開發人員體驗,因為可以從 HTML 推斷出許多事情。
控制產生的 HTML 也允許預設更積極地最佳化。目前,我們預設防止重新命名或分割初始區塊,因為這需要額外的基礎架構來產生 HTML。
HTML 進入點也受益於 CSS 作為模組和資產模組,因為這些資源也可以從 HTML 參照(例如 <link rel=stylesheet />
、<img src="..." />
、<link rel=icon />
)。
還有一個關於瀏覽器原生支援匯入 HTML 的提案,這是我們將遵循的,特別是因為它與 HTML 進入點有很大的重疊。
目前在 webpack 中使用(完整)SourceMap 非常昂貴,因為 SourceMap 處理的效能並非最佳。這是我們希望在 webpack 中研究的,同時也是在 terser 中研究的,而 terser 是 webpack 預設使用的最小化程式。
exports
/imports
package.json 欄位Node.js 14 已新增支援 package.json 中的 exports
欄位,以允許定義套件的進入點。Webpack 5 遵循此做法,甚至新增其他條件,例如 production/development
。
不久之後,Node.js 對此進行了進一步的補充,例如,他們還新增了一個 imports
欄位,用於私人匯入。
這也是我們想要新增的功能。
儘管 ESM 是未來趨勢,但 npm 中仍有許多 CommonJS 套件正在使用中。Webpack 5 新增了對 CommonJS 模組的分析,以便對大多數這些模組進行 Tree Shaking。
但我們可以做得更多。儘管支援許多匯出模式,但僅支援少數匯入模式。我們希望新增對更多模式的支援,以允許對 CommonJS 模組進行更多最佳化。
Webpack 5 新增了一個名為「模組聯合」的新功能,允許在執行階段整合多個建置。目前,熱模組替換 (HMR) 一次僅支援單一建置,且更新無法在建置之間傳遞。我們希望在此方面進行改善,並允許 HMR 更新在不同建置之間傳遞,這將有助於開發聯合應用程式。
目前,webpack 會向使用者顯示警告和錯誤。在建置過程中,有許多情況下我們可以告訴使用者一些事情,例如潛在的陷阱或最佳化機會,但這些情況不符合警告或錯誤,而且我們不希望用所有這些資訊來發送垃圾郵件。因此,我們希望新增另一個類別:提示。我們希望在建置過程中收集所有提示(外掛程式也可以發出一些提示),但僅在輸出中顯示其中有限的數量(預設只顯示一個)。這應該會為使用者帶來一種「您知道嗎」的體驗。
持續快取讓快取建置「極速」進行,但沒有持續快取的初始建置仍有進步空間。預設情況下,Node.js 中的 Javascript 執行是單執行緒,但最近新增的功能允許使用類似於 WebWorkers 的 API worker_threads
。
這可以用於在所有 CPU 上分配工作。webpack 5 中已針對此進行一些準備:例如,內部資料結構的序列化是可能的,而且工作佇列支援外掛程式。但其中一些部分仍不清楚,需要進行實驗。
這已在我們的投票清單中一段時間,但沒有很多人投票。這真的是人們需要的嗎?
目前,WebAssembly 仍處於實驗階段,且預設未啟用。一旦提案進入第 4 階段,我們就可以預設啟用它。
這也可能導致生態系統中更廣泛地採用 WebAssembly。我認為我們可能在 2021 年看到更多這方面的領域。
此清單並非一成不變。網路生態系統變化如此之快,我們最終可能會實作完全不同的東西,甚至在此刻我們可能還不知道。考慮到我們目前的贊助狀況,我們甚至不知道可以在 webpack 上投入多少時間。