序
最近,我在觀察webpack是如何做持久緩存的,我發現其中仍然有壹些坑。我只是有時間整理和總結它們。讀完這篇文章,妳可以大致了解:
什麽是持久緩存,為什麽要這麽做?
webpack是如何實現持久緩存的?
webpack緩存的壹些註意事項。
永久緩存
首先,我們需要解釋壹下什麽是持久緩存。在前後端分離的應用程序流行的背景下,前端的html、css和js往往以靜態資源文件的形式存在於服務器中,通過接口獲取數據以展示動態內容。這涉及到公司如何部署前端代碼,所以涉及到壹個更新部署問題,是先部署頁面還是先部署資源?
首先部署頁面,然後部署資源:在兩次部署之間的時間間隔內,如果用戶訪問頁面,舊的資源將被加載到新的頁面結構中,舊版本的資源將被緩存為新版本。因此,用戶訪問的頁面風格混亂,除非手動刷新,否則頁面將壹直處於混亂狀態,直到資源緩存過期。
首先部署資源,然後部署頁面:在部署間隔期間,具有舊版本資源的本地緩存的用戶訪問網站。因為請求的頁面是舊版本並且資源引用沒有改變,所以瀏覽器將直接使用本地緩存,這是正常的。但是,當沒有本地緩存或緩存過期的用戶訪問網站時,舊版本的頁面將加載新版本的資源,從而導致頁面執行錯誤。
因此,我們需要壹種部署策略來確保在線用戶在更新我們的在線代碼時可以順利過渡並正確打開我們的網站。
我建議先閱讀這個答案:如何在大公司中開發和部署前端代碼?
看完上面的回答,妳壹般會明白比較成熟的持久化緩存方案是在靜態資源的名稱後面加壹個哈希值,因為每次文件修改產生的哈希值是不壹樣的。這樣做的好處是以增量方式發布文件,從而避免覆蓋先前的文件,這將導致在線用戶訪問失敗。
因為只要每次發布的靜態資源(css、js、img)的名稱是唯壹的,那麽我就可以:
對於html文件:不要打開緩存,將html放在您自己的服務器上,並關閉服務器的緩存。妳自己的服務器只提供html文件和數據接口。
對於靜態js、css、圖片等文件:打開cdn和緩存,將靜態資源上傳到cdn服務商。我們可以為資源打開長期緩存,因為每個資源的路徑都是唯壹的,所以不會導致資源被覆蓋,保證了在線用戶訪問的穩定性。
每次發布更新時,都會先將靜態資源(js、css、img)上傳到cdn服務中,然後再上傳html文件,這樣既保證了老用戶可以正常訪問,又能讓新用戶看到新頁面。
前面介紹了主流的前端持久緩存方案,那麽我們為什麽需要持久緩存呢?
當用戶第壹次使用瀏覽器訪問我們的網站時,頁面會介紹各種靜態資源。如果我們可以持久化緩存,我們可以在/happylindz/blog.git找到它。
cd博客/代碼/多頁網絡包-演示
Npm安裝在閱讀以下內容之前,我強烈建議您閱讀我之前的文章:深入了解webpack文件的打包機制將有助於您更好地實現持久緩存。
該示例描述如下:它由兩個頁面pageA和pageB組成。
// src/pageA.js
從‘導入組件a。/common/componentA‘;
//如果使用jquery第三方庫,需要將其拉出來,避免業務包文件過大。
從“jquery”導入$時;
//加載css文件,有些是公共* * *樣式,有些是獨特樣式,需要拉取。
“導入”。/CSS/common . CSS‘
“導入”。/CSS/pagea . CSS‘;
console . log(componentA);
console . log($。trim(‘做某事’);
// src/pageB.js
//頁面A和頁面B都使用了公共模塊componentA,需要移除該模塊以避免重復加載。
從‘導入組件a。/common/componentA‘;
從‘導入組件b。/common/componentB‘;
“導入”。/CSS/common . CSS‘
“導入”。/CSS/pageb . CSS‘;
console . log(componentA);
console . log(componentB);
//需要拉出異步加載模塊asyncComponent來加載第壹個屏幕速度。
document . getelementbyid(‘xxxxx‘)。addevent listener(‘click‘,()= & gt{
導入(/* webpackChunkName:“async“*/
。/common/async component . js’)。然後((async)= & gt;{
async();
})
})
//男* * *模塊基本上是這樣的。
導出默認的“組件X”;以上頁面內容基本涉及到我們模塊拆分的三種模式:拆分公共庫、按需加載和拆分公共模塊。因此下壹步是配置webpack:
const path = require(‘path‘);
const web pack = require(‘web pack‘);
const extract text plugin = require(‘extract-text-web pack-plugin‘);
module.exports = {
條目:{
pageA:【path . resolve(dirname,‘。/src/pagea . js‘)】,
pageB:path . resolve(dirname,‘。/src/pageb . js’),
},
輸出:{
路徑:path.resolve(目錄名,‘。/dist‘),
文件名:“js/【名稱】。【chunkhash:8】。js‘,
chunk filename:‘js/【name】。【chunkhash:8】。js
},
模塊:{
規則:【
{
//使用正則化來匹配此加載程序要轉換的CSS文件。
測試:/。css$/,
使用:extract text plugin . extract ({
後備:“樣式加載器”,
使用:【“css-loader“】
})
}
]
},
插件:【
新建web pack . optimize . commonschunkplugin ({
名稱:“常用”,
minChunks: 2,
}),
新建web pack . optimize . commonschunkplugin ({
名稱:‘供應商‘,
min chunks:({ resource })= & gt;(
資源與環境。& ampresource . index of(‘node _ modules‘)& gt;= 0 & amp& ampresource . match(/)。js$/)
)
}),
新的ExtractTextPlugin ({
文件名:` CSS/【name】。【chunk hash:8】。CSS `,
}),
]
}第壹個CommonsChunkPlugin用於提取公共* * *模塊,相當於webpack bosses。如果妳看到壹個模塊被加載了兩次或更多次,請幫我把它移到common chunk,其中minChunks是2,粒度是最細的。您可以根據自己的實際情況選擇提取次數。
第二個CommonsChunkPlugin用於提取第三方代碼,提取它們並判斷資源是否來自node_modules。如果是,說明它們是第三方模塊,然後提取它們。這相當於告訴webpack老板,如果您看到壹些模塊來自node_modules目錄並且它們的名稱以結尾。js,請將它們全部移動到供應商區塊,如果供應商區塊不存在,請創建壹個新的。
這種配置有什麽優勢?隨著業務的增長,我們可能會越來越多地依賴第三方庫代碼。如果我們專門配置壹個門戶來存儲第三方代碼,我們的webpack.config.js將變成:
//不利於擴張
module.exports = {
條目:{
應用程序:‘‘。/src/main . js‘,
供應商:【
vue,
axio,
vue路由器‘,
vuex,
//更多
],
},
}第三個ExtractTextPlugin插件用於從打包的js文件中提取css並生成獨立的css文件。想象壹下,當您只是修改樣式而不修改頁面的功能邏輯時,您肯定不希望js文件的哈希值發生變化。妳肯定希望css和js相互分離,互不影響。
您可以在運行webpack後看到打包的效果:
├── css
│ ├── common.2beb7387.css
│ ├── pageA.d178426d.css
│└──pageb . 33931188 . CSS
└── js
├── async.03f28faf.js
├──公共區
├── pageA.d178426d.js
├──頁面B.33931188.js
└── vendor.22a1d956.js可以看出css和js已經分離,我們對模塊進行了拆分以確保模塊塊的唯壹性。每次更新代碼時,都會生成不同的哈希值。
唯壹性,那麽我們就需要保證哈希值的穩定性。想象這樣壹個場景。妳肯定不希望通過修改代碼的某個部分(模塊、css)來更改文件的哈希值,因此這顯然是不明智的。那麽我們如何最小化哈希值的變化呢?
換句話說,我們應該找出webpack編譯中導致緩存失效的因素,並嘗試解決或優化它?
chunkhash值的變化主要由以下四部分引起:
包含模塊的源代碼。
webpack用來開始運行的運行時代碼。
webpack生成的Moduleid(包括模塊id和引用的相關模塊id)
chunkID
只要這四個部分中的任何壹個部分發生變化,生成的塊文件就會不同,緩存就會無效。這裏有四個部分:
首先,源代碼變更:
顯然,不言而喻,緩存必須刷新,否則將出現問題。
二、webpack啟動的運行時代碼:
看完我之前的文章:深入理解webpack文件的打包機制,妳就會知道在啟動webpack時需要執行壹些啟動代碼。
(功能(模塊){
window【“webpackysonp“】=函數webpackysonpcallback(chunkIds,more modules ){
// ...
};
函數web pack _ require(moduleId ){
// ...
}
webpack_require.e =函數require assure(chunk id,callback ){
// ...
script . src = web pack _ require . p+““+chunkId+“。“+({“0“:“pageA“、“1“:“pageB“、“3“:“vendor“}【chunkId】| | chunkId)+“。“+{“0“:“e 72 ce7 d 4“、“1“:“69f 6 bbe 3“、“2“:“9 adbbaa 0“、“3“:“53fa 02 a 7“}【chunkId】+“。js”;
};
})([]);大致內容如上,它們是webpack的壹些啟動代碼,是壹些告訴瀏覽器如何加載webpack定義的模塊的函數。
每次更新都會有壹行代碼發生變化,因為啟動代碼需要清楚地知道chunkid和chunkhash值之間的對應關系,這樣才能在異步加載時正確拼接異步js文件的路徑。
那麽這部分代碼最終出現在哪個文件中呢?由於我們剛剛配置時最後生成的common chunk模塊,這部分運行時代碼將直接構建在其中,這導致我們每次更新業務代碼(pageA,pageB,module)時,common chunkhash總是會發生變化,但這顯然不符合我們的預期。因為我們只想使用common chunk來存儲公共模塊(在本例中為componentA),所以我沒有修改componentA,那麽為什麽chunkhash需要更改呢?
因此我們需要將這部分運行時代碼提取到單獨的文件中。
module.exports = {
// ...
插件:【
// ...
//將其放在其他CommonsChunkPlugin後面
新建web pack . optimize . commonschunkplugin ({
名稱:“運行時”,
minChunks:無窮大,
}),
]
}這相當於告訴webpack幫我提取運行時代碼並將其放在壹個單獨的文件中。
├── css
│ ├── common.4cc08e4d.css
│ ├── pageA.d178426d.css
│└──pageb . 33931188 . CSS
└── js
├── async.03f28faf.js
├── common.4cc08e4d.js
├── pageA.d178426d.js
├──頁面B.33931188.js
├──運行時間。8c 79 fdcd。js
└── vendor.cef44292.js生成壹個額外的runtime.xxxx.js當您將來更改業務代碼時,公共塊的哈希值不會更改,而運行時塊的哈希值會更改。由於這部分代碼是動態的,您可以通過chunk-manifest-webpack-plugin將其內聯到html中以減少壹個。
第三,由webpack生成的模塊moduleid
默認情況下,在webpack2中加載OccurrenceOrderPlugin。OccurrenceOrderPlugin將按引入次數最多的模塊進行排序。已經引入的模塊的moduleId越小,它仍然不穩定。隨著妳的代碼量的增加,雖然帶有代碼引用的模塊的moduleId越小,不容易更改,但它不可避免地具有不確定性。
默認情況下,模塊的id是該模塊在模塊數組中的索引。OccurenceOrderPlugin會將經常引用的模塊放在前面,並且模塊的順序在每次編譯時都是壹致的。如果在修改代碼時添加或刪除某些模塊,這可能會影響所有模塊的id。
最佳實踐是使用HashedModuleIdsPlugin插件,該插件將根據模塊的相對路徑生成壹個長度僅為四位數的字符串作為模塊id,這不僅隱藏了模塊的路徑信息,還減少了模塊id的長度。
這樣,更改moduleId的唯壹方法就是更改文件路徑。只要您的文件路徑值保持不變,生成的四位數字符串將保持不變,哈希值也將保持不變。添加或刪除業務代碼模塊不會對moduleid產生任何影響。
module.exports = {
插件:【
新網絡包。HashedModuleIdsPlugin(),
//放在最前面
// ...
]
}第四,chunkID
事實上,塊數的順序在多次編譯之間大多是固定的,不容易改變。
這裏涉及的只是基本的模塊拆分,還有壹些其他情況沒有考慮。例如,異步加載組件包含壹個陽性模塊,陽性模塊可以再次被拉走。形成異步公共塊模塊。任何想要深入研究的人都可以閱讀這篇文章:Webpack解決方案的代碼拆分。
webpack緩存的壹些註意事項
CSS文件的無效哈希值問題
不建議使用DllPlugin插件進行在線發布。
CSS文件的哈希值無效;
ExtractTextPlugin有壹個嚴重的問題,即它用來生成文件名的【chunkhash】直接取自引用css代碼段的JSChunk換句話說,如果我只是修改了css代碼段而沒有移動js代碼,那麽最終生成的css文件名保持不變。
因此,我們需要將ExtractTextPlugin中的chunkhash更改為contenthash,顧名思義,content hash表示文本文件內容的哈希值,即僅表示樣式文件的哈希值。以這種方式編譯的js和css文件具有獨立的哈希值。
module.exports = {
插件:【
// ...
新的ExtractTextPlugin ({
文件名:` css