為什么需要提取公共代碼
大型網站通常會由多個頁面組成,每個頁面都是一個獨立的單頁應用。 但由於所有頁面都采用同樣的技術棧,以及使用同一套樣式代碼,這導致這些頁面之間有很多相同的代碼。
如果每個頁面的代碼都把這些公共的部分包含進去,會造成以下問題:
- 相同的資源被重復的加載,浪費用戶的流量和服務器的成本;
- 每個頁面需要加載的資源太大,導致網頁首屏加載緩慢,影響用戶體驗。
如果把多個頁面公共的代碼抽離成單獨的文件,就能優化以上問題。 原因是假如用戶訪問了網站的其中一個網頁,那么訪問這個網站下的其它網頁的概率將非常大。 在用戶第一次訪問后,這些頁面公共代碼的文件已經被瀏覽器緩存起來,在用戶切換到其它頁面時,存放公共代碼的文件就不會再重新加載,而是直接從緩存中獲取。 這樣做后有如下好處:
- 減少網絡傳輸流量,降低服務器成本;
- 雖然用戶第一次打開網站的速度得不到優化,但之后訪問其它頁面的速度將大大提升。
如何提取公共代碼
你已經知道了提取公共代碼會有什么好處,但是在實戰中具體要怎么做,以達到效果最優呢? 通常你可以采用以下原則去為你的網站提取公共代碼:
- 根據你網站所使用的技術棧,找出網站所有頁面都需要用到的基礎庫,以采用 React 技術棧的網站為例,所有頁面都會依賴 react、react-dom 等庫,把它們提取到一個單獨的文件。 一般把這個文件叫做
base.js
,因為它包含所有網頁的基礎運行環境; - 在剔除了各個頁面中被
base.js
包含的部分代碼外,再找出所有頁面都依賴的公共部分的代碼提取出來放到common.js
中去。 - 再為每個網頁都生成一個單獨的文件,這個文件中不再包含 base.js 和 common.js 中包含的部分,而只包含各個頁面單獨需要的部分代碼。
文件之間的結構圖如下:
讀到這里你可以會有疑問:既然能找出所有頁面都依賴的公共代碼,並提取出來放到 common.js 中去,為什么還需要再把網站所有頁面都需要用到的基礎庫提取到 base.js 去呢? 原因是為了長期的緩存 base.js 這個文件。
發布到線上的文件都會采用在4-9CDN加速中介紹過的方法,對靜態文件的文件名都附加根據文件內容計算出 Hash 值,也就是最終 base.js 的文件名會變成 base_3b1682ac.js ,以長期緩存文件。 網站通常會不斷的更新發布,每次發布都會導致 common.js
和各個網頁的 JavaScript 文件都會因為文件內容發生變化而導致其 Hash 值被更新,也就是緩存被更新。
把所有頁面都需要用到的基礎庫提取到 base.js
的好處在於只要不升級基礎庫的版本,base.js
的文件內容就不會變化,Hash 值不會被更新,緩存就不會被更新。 每次發布瀏覽器都會使用被緩存的 base.js
文件,而不用去重新下載 base.js
文件。 由於 base.js
通常會很大,這對提升網頁加速速度能起到很大的效果。
如何通過 Webpack 提取公共代碼
你已經知道如何提取公共代碼,接下來教你如何用 Webpack 實現。
Webpack 內置了專門用於提取多個 Chunk 中公共部分的插件 CommonsChunkPlugin , CommonsChunkPlugin 大致使用方法如下:
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); new CommonsChunkPlugin({ // 從哪些 Chunk 中提取 chunks: ['a', 'b'], // 提取出的公共部分形成一個新的 Chunk,這個新 Chunk 的名稱 name: 'common' })
以上配置就能從網頁 A 和網頁 B 中抽離出公共部分,放到 common 中。
每個 CommonsChunkPlugin 實例都會生成一個新的 Chunk,這個新 Chunk 中包含了被提取出的代碼,在使用過程中必須指定 name
屬性,以告訴插件新生成的 Chunk 的名稱。 其中 chunks
屬性指明從哪些已有的 Chunk 中提取,如果不填該屬性,則默認會從所有已知的 Chunk 中提取。Chunk 是一系列文件的集合,一個 Chunk 中會包含這個 Chunk 的入口文件和入口文件依賴的文件。
通過以上配置輸出的 common Chunk 中會包含所有頁面都依賴的基礎運行庫 react、react-dom,為了把基礎運行庫從 common 中抽離到 base 中去,還需要做一些處理。
首先需要先配置一個 Chunk,這個 Chunk 中只依賴所有頁面都依賴的基礎庫以及所有頁面都使用的樣式,為此需要在項目中寫一個文件 base.js
來描述 base Chunk 所依賴的模塊,文件內容如下:
// 所有頁面都依賴的基礎庫 import 'react'; import 'react-dom'; // 所有頁面都使用的樣式 import './base.css';
接着再修改 Webpack 配置,在 entry 中加入 base,相關修改如下:
module.exports = { entry: { base: './base.js' }, };
以上就完成了對新 Chunk base 的配置。
為了從 common 中提取出 base 也包含的部分,還需要配置一個 CommonsChunkPlugin,相關代碼如下:
new CommonsChunkPlugin({ // 從 common 和 base 兩個現成的 Chunk 中提取公共的部分 chunks: ['common', 'base'], // 把公共的部分放到 base 中 name: 'base' })
由於 common 和 base 公共的部分就是 base 目前已經包含的部分,所以這樣配置后 common 將會變小,而 base 將保持不變。
以上都配置好后重新執行構建,你將會得到四個文件,它們分別是:
base.js:所有網頁都依賴的基礎庫組成的代碼;
common.js:網頁A、B都需要的,但又不在 base.js 文件中出現過的代碼;
a.js:網頁 A 單獨需要的代碼;
b.js:網頁 B 單獨需要的代碼。
為了讓網頁正常運行,以網頁 A 為例,你需要在其 HTML 中按照以下順序引入以下文件才能讓網頁正常運行:
<script src="base.js"></script> <script src="common.js"></script> <script src="a.js"></script>
以上就完成了提取公共代碼需要的所有步驟。
針對 CSS 資源,以上理論和方法同樣有效,也就是說你也可以對 CSS 文件做同樣的優化。
以上方法可能會出現 common.js
中沒有代碼的情況,原因是去掉基礎運行庫外很難再找到所有頁面都會用上的模塊。 在出現這種情況時,你可以采取以下做法之一:
- CommonsChunkPlugin 提供一個選項
minChunks
,表示文件要被提取出來時需要在指定的 Chunks 中最小出現最小次數。 假如 minChunks=2、chunks=['a','b','c','d'] ,任何一個文件只要在 ['a','b','c','d'] 中任意兩個以上的 Chunk 中都出現過,這個文件就會被提取出來。 你可以根據自己的需求去調整 minChunks 的值,minChunks 越小越多的文件會被提取到common.js
中去,但這也會導致部分頁面加載的不相關的資源越多; minChunks 越大越少的文件會被提取到common.js
中去,但這會導致common.js
變小、效果變弱。 - 根據各個頁面之間的相關性選取其中的部分頁面用 CommonsChunkPlugin 去提取這部分被選出的頁面的公共部分,而不是提取所有頁面的公共部分,而且這樣的操作可以疊加多次。 這樣做的效果會很好,但缺點是配置復雜,你需要根據頁面之間的關系去思考如何配置,該方法不通用。本實例提供項目完整代碼