Next.js 和 Gatsby 採用較新的 webpack 分割策略,可減少重複程式碼,提升網頁載入效能。
Chrome 會與 JavaScript 開放原始碼生態系統中的工具和架構合作。我們最近新增了許多新最佳化功能,以改善 Next.js 和 Gatsby 的載入效能。本文將說明經過改善的精細區塊化策略,目前這項策略已預設提供給這兩個架構。
簡介
與許多網頁架構一樣,Next.js 和 Gatsby 都使用 webpack 做為核心套件組合器。webpack 3.0 推出 CommonsChunkPlugin
,讓您可以在單一 (或少數)「共用」區塊 (或區塊) 中,針對不同的進入點輸出共用的模組。共用程式碼可單獨下載,並提早儲存在瀏覽器快取中,進而提升載入效能。
許多單頁應用程式架構採用的進入點和套件組態,其模式如下所示:
雖然這項做法實用,但將所有共用模組程式碼綁定至單一區塊的概念有其限制。未在每個進入點共用的模組,可能會為不使用該模組的路徑下載,導致下載的程式碼比實際需要的多。舉例來說,當 page1
載入 common
區塊時,即使 page1
未使用 moduleC
,它仍會載入 moduleC
的程式碼。因此,除了其他幾個原因外,webpack 4 也移除了這個外掛程式,改用新的外掛程式: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 中使用多工處理功能,可透過單一來源使用單一連線,並同時傳送多個要求。換句話說,我們通常不必擔心限制 Bundler 傳送的區塊數量。
所有主要瀏覽器都支援 HTTP/2。Chrome 和 Next.js 團隊想瞭解,如果將 Next.js 的單一「commons」套件分割成多個共用區塊,藉此增加請求數量,是否會對載入效能造成任何影響。他們首先評估單一網站的效能,並使用 maxInitialRequests
屬性修改並行要求的數量上限。
在單一網頁上平均執行三次多項測試時,當變更最大初始要求數量 (從 5 到 15),load
、開始算繪和首次顯示內容所需時間的時間都維持不變。有趣的是,我們發現只有在將要求分割為數百個時,才會出現輕微的效能開銷。
這項測試顯示,只要維持在可靠的門檻 (20 至 25 個要求) 以下,就能在載入效能和快取效率之間取得平衡。經過一些基準測試後,我們選定 25 為 maxInitialRequest
的計數。
修改並行發生的要求數量上限後,會產生多個共用套件,並為每個進入點適當地分隔這些套件,大幅減少同一個網頁的多餘程式碼數量。
這項實驗只是為了修改要求數量,看看是否會對網頁載入效能造成任何負面影響。結果顯示,在測試頁面上將 maxInitialRequests
設為 25
是最佳做法,因為這樣可以縮減 JavaScript 酬載大小,且不會減緩網頁速度。為了重新整理網頁,所需的 JavaScript 總量仍維持不變,這也是為何網頁載入效能並未因減少的程式碼量而提升的原因。
webpack 會將 30 KB 做為產生區塊的預設最小大小。不過,將 maxInitialRequests
值 25 與 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)[\\/]/,
},
他們也透過最佳化 webpack 設定採用類似的細微分割策略,發現許多大型網站的 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
。