Next.js 和 Gatsby 中較新的 webpack 區塊策略可減少重複的程式碼,進而提升網頁載入效能。
Chrome 正在與 JavaScript 開放原始碼生態系統中的工具和架構合作。我們最近新增了多項最佳化功能,可提升 Next.js 和 Gatsby 的載入效能。本文將介紹改良的細部分塊策略,現在這兩個架構預設都會提供這項策略。
簡介
與許多網頁架構一樣,Next.js 和 Gatsby 都使用 webpack 做為核心套件組合工具。webpack v3 導入了 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 個),因此盡量減少打包工具輸出的區塊數量,可確保要求總數維持在這個門檻以下。不過,這只適用於 HTTP/1.1。HTTP/2 中的多工處理功能可透過單一來源的單一連線,並行串流多個要求。換句話說,我們通常不必擔心限制 bundler 發出的區塊數量。
所有主要瀏覽器都支援 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
值設為 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 設定中實作這項邏輯,而這項設定預設會隨附於 2.20.7 版。
結論
細部區塊的運送概念不限於 Next.js、Gatsby,甚至 webpack。如果應用程式採用大型「commons」套件方法,無論使用哪個架構或模組套件工具,都應考慮改善應用程式的區塊策略。
- 如要瞭解如何將相同的區塊最佳化套用至原生 React 應用程式,請參閱這個 React 應用程式範例。這個範例採用簡化的細微區塊策略,可協助您開始將相同的邏輯套用至網站。
- 對於 Rollup,系統預設會以細微程度建立區塊。如要手動設定這項行為,請參閱
manualChunks
。