Next.js 和 Gatsby 採用較新的 webpack 分割策略,可減少重複程式碼,進而提升網頁載入效能。
Chrome 會與 JavaScript 開放原始碼生態系統中的工具和架構合作。我們最近新增了許多新功能,以改善 Next.js 和 Gatsby 的載入效能。本文將說明經過改善的精細區塊化策略,目前這項策略已預設提供這兩個架構。
簡介
與許多網頁架構一樣,Next.js 和 Gatsby 都使用 webpack 做為核心套件組合器。webpack 3.0 推出 CommonsChunkPlugin
,讓您可以在單一 (或少數)「共用」區塊 (或區塊) 中,針對不同的進入點輸出共用的模組。共用程式碼可單獨下載,並提早儲存在瀏覽器快取中,進而提升載入效能。
許多單頁應用程式架構採用的進入點和套件組態,其模式如下所示:
雖然這項做法實用,但將所有共用模組程式碼綁定至單一區塊的概念有其限制。未在每個進入點共用的模組,可能會為不使用該模組的路徑下載,導致下載的程式碼比實際需要的多。舉例來說,當 page1
載入 common
區塊時,即使 page1
未使用 moduleC
,它仍會載入 moduleC
的程式碼。因此,Webpack v4 並移除了外掛程式,並改用新版外掛程式:SplitChunksPlugin
。
改善分割功能
SplitChunksPlugin
的預設設定適用於大多數使用者。系統會根據多項條件建立多個分割區塊,以免在多個路徑中擷取重複的程式碼。
不過,許多使用這個外掛程式的網頁架構仍採用「單一通用」方式來分割區塊。舉例來說,Next.js 會產生 commons
軟體包,其中包含在超過 50% 網頁和所有架構依附元件 (react
、react-dom
等) 中使用的模組。
const splitChunksConfigs = {
…
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
},
},
},
雖然將框架相依的程式碼加入共用區塊,代表可為任何進入點下載及快取,但根據使用情況加入超過一半頁面所使用的常見模組,這種以使用者為依據的啟發式搜尋方式並不十分有效。修改此比率只會導致下列兩種結果之一:
- 如果您降低比率,系統就會下載更多不必要的程式碼。
- 如果您提高比率,則多條路線上會有更多程式碼。
為解決這個問題,Next.js 採用了 SplitChunksPlugin
的不同設定,可減少任何路徑的多餘程式碼。
- 任何足夠大的第三方模組 (大於 160 KB) 都會拆分為個別的區塊
- 為框架依附元件 (
react
、react-dom
等) 建立個別的frameworks
區塊 - 建立所需數量的共用區塊 (最多 25 個)
- 要產生的區塊大小下限變更為 20 KB
這種精細的區塊化策略具有下列優點:
- 網頁載入時間縮短。發送多個共用區塊,而非單一區塊,可盡量減少任何進入點的非必要 (或重複) 程式碼的數量。
- 改善導覽期間的快取功能。將大型程式庫和架構依附元件拆分為個別區塊,可降低快取失效的可能性,因為在升級前,這兩者都不會變更。
您可以在 webpack-config.ts
中查看 Next.js 採用的完整設定。
更多 HTTP 要求
SplitChunksPlugin
定義了細微區塊處理的基礎,將這項做法套用至 Next.js 等架構並非全新概念。不過,許多架構仍會繼續使用單一啟發法和「常見」套件策略,原因有幾個。包括擔心更多 HTTP 要求可能會對網站效能造成負面影響。
瀏覽器只能開啟有限數量的 TCP 連線至單一來源 (Chrome 為 6 個),因此減少 Bundler 輸出的區塊數量,可確保要求總數低於此門檻。不過,這項規則僅適用於 HTTP/1.1。透過 HTTP/2 的多工處理功能,您可以透過單一來源使用單一連線,並同時傳送多個要求。換句話說,我們通常不需要擔心組合器發出的區塊數量。
所有主要瀏覽器都支援 HTTP/2。Chrome 和 Next.js 團隊想瞭解,如果將 Next.js 的單一「commons」套件分割成多個共用區塊,藉此增加請求數量,是否會對載入效能造成任何影響。他們首先評估單一網站的效能,並使用 maxInitialRequests
屬性修改並行要求的數量上限。
平均在單一網頁上執行三次試驗時,當初始要求數上限 (從 5 到 15 個) 變動時,load
、start-render和首次內容繪製時間會保持不變。有趣的是,我們發現只有在將要求分割為數百個時,才會出現輕微的效能開銷。
這項測試顯示,只要維持在可靠的門檻 (20 至 25 個要求) 以下,就能在載入效能和快取效率之間取得平衡。經過一些基準測試後,我們選定 25 為 maxInitialRequest
的計數。
修改同時發生的要求數量上限會使系統產生多個共用套件,並將每個進入點妥善分隔成各個進入點,大幅減少了同一頁面中不必要的程式碼數量。
這項實驗只是為了修改要求數量,看看是否會對網頁載入效能產生負面影響。結果顯示,在測試頁面上將 maxInitialRequests
設為 25
是最佳做法,因為這樣可以縮減 JavaScript 酬載大小,且不會減緩網頁速度。為了重新整理網頁,所需的 JavaScript 總量仍維持不變,這也是為何網頁載入效能並未因減少的程式碼量而提升的原因。
webpack 以 30 KB 做為產生區塊的預設大小下限。不過,將 maxInitialRequests
值與大小下限為 20 KB 相耦合,則有利於提高快取效能。
使用精細區塊縮減大小
許多架構 (包括 Next.js) 都會依賴用戶端導覽 (由 JavaScript 處理),為每個路徑轉換注入較新的指令碼標記。但他們如何在建構期間預先決定這些動態區塊?
Next.js 會使用伺服器端建構資訊清單檔案,判斷不同進入點會使用哪些輸出區塊。為了同時向用戶端提供這項資訊,我們建立了精簡的用戶端建構資訊清單檔案,用於對應每個進入點的所有依附元件。
// Returns a promise for the dependencies for a particular route
getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
)
}
這項較新的細部區塊化策略最初是在 Next.js 中透過標記推出,並在許多早期採用者身上進行測試。許多網站的 JavaScript 總用量都大幅減少:
網站 | JS 總變化 | 差異百分比 |
---|---|---|
https://www.barnebys.com/ | -238 KB | -23% |
https://sumup.com/ | -220 KB | -30% |
https://www.hashicorp.com/ | -11 MB | -71% |
根據預設,最終版本推出於 9.2 版。
Gatsby
Gatsby 曾採用相同的方法,使用以使用量為依據的啟發式來定義常見模組:
config.optimization = {
…
splitChunks: {
name: false,
chunks: `all`,
cacheGroups: {
default: false,
vendors: false,
commons: {
name: `commons`,
chunks: `all`,
// if a chunk is used more than half the components count,
// we can assume it's pretty global
minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
},
react: {
name: `commons`,
chunks: `all`,
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
},
在最佳化 Web Pack 設定以採用類似的精細分塊策略後,他們也注意到許多大型網站的 JavaScript 可以縮減大小:
網站 | JS 總變化 | 差異百分比 |
---|---|---|
https://www.gatsbyjs.org/ | -680 KB | -22% |
https://www.thirdandgrove.com/ | -390 KB | -25% |
https://ghost.org/ | -1.1 MB | -35% |
https://reactjs.org/ | -80 Kb | -8% |
請參閱PR,瞭解他們如何將這項邏輯實作至 webpack 設定中,這項設定會在 v2.20.7 中預設提供。
結論
提交精細區塊的概念並非 Next.js、Gatsby 或 webpack 專屬。如果應用程式遵循大型的「常見」組合方法,無論使用何種架構或模組組合工具,所有人都應考慮改善應用程式的分塊策略。
- 如果您想查看相同的區塊最佳化方式,並套用至一般 React 應用程式,請查看這個 React 應用程式範例。這個範例採用簡化的細目區塊策略,可協助您開始將相同類型的邏輯套用至網站。
- 對於匯總,系統預設會逐一建立區塊。如要手動設定行為,請參閱
manualChunks
。