持久快取
使用webpack構建工程的時候,我們往常會把功能不同的程式碼打包到不同的包裡(如lib,vendor,業務程式碼)。 而持久快取的目的就是每一次更新線上程式碼的時候,儘可能使內容未做更改的模組的名字和之前保持一致。
使用webpack實現持久快取主要需要解決:
-
webpack runtime程式碼分離
-
穩定moduleID
-
穩定chunkID
基本配置
webpack有提供2種hash命名的方式,
-
[hash]: 整次build生成一個唯一的hash值,賦給所有生成的檔案。
顯然,為了將檔名和內容相關聯,應該使用chunkhash。
output: {
path: path.join(__dirname, `dist/js`),
filename: `[name]-[chunkhash:8].js`,
chunkFilename: `chunks/[name].[chunkhash:8].js`
},
複製程式碼
這樣配置雖然可以實現持久快取,但是把所有的程式碼都打到了一起(vendor, 業務程式碼等),顯示不符合要求。
webpack-runtime
使用CommonsChunkplugin將vendor單獨打包:
new webpack.optimize.CommonsChunkPlugin({
name: `vendor`,
minChunks: function(module) {
return /node_modules/.test(module.context);
}
})
複製程式碼
現在如果我對業務程式碼進行更改:
const helloWorld = () => {
console.log(`moment : ${moment()}`);
};
複製程式碼
按道理vendor已經單獨打包,改變業務程式碼並不應該改變vendor的hash值,然而
原因是CommonsChunkplugin把vendor單獨打出來的時候,還會將webpack自己生成的一部分runtime程式碼一起打進vendor. 如下圖,runtime裡牽扯到chunkid等容易頻繁變更的元素,所以當業務程式碼發生變化的時候,runtime程式碼也會變。
這個問題也容易解決,再用CommonsChunkplugin把runtime程式碼單獨打出來(CommonsChunkplugin會把runtime的程式碼打到配置指定的最後一個chunk裡):
new webpack.optimize.CommonsChunkPlugin({
name: `manifest`,
minChunks: Infinity
})
複製程式碼
.
現在再更改業務程式碼,業務程式碼和manifest的內容會變,vendor檔案的內容不會受影響。
Module ID
然而還沒完,當我們在業務程式碼裡增加一個entry,vendor的hash值又發生了變化。
entry: {
main: "./src/index.tsx",
sub: "./src/sub.tsx",
},
複製程式碼
造成這個問題的原因是當我們加入一個新entry的時候,因為webpack預設會依次用整數給這些module命名,如果新增的entry有新引入其他module,就會打亂之前module的命名。比如說當只有一個entry的時候,業務程式碼裡定義了module: 0,1,2,3。vendor裡定義了module 4,5。增加一個entry後:業務程式碼裡會定義: 0,1,2,3,4。 vendor裡定義module 5,6.
(其實,就算sub沒有引入新module,這裡如果把main和sub的位置換一下也會造成vendor hash值變化,這和chunkID的值有關,後面會介紹)
可見,要生成穩定的chunkhash值,首先必須解決moduleID的問題。
NamedModulesPlugin & HashedModuleIdsPlugin
NamedModulesPlugin:使用檔案的相對路徑代替整數作為moduleID.
var isNumber = __webpack_require__("./node_modules/array-first/node_modules/is-number/index.js");
var slice = __webpack_require__("./node_modules/array-slice/index.js");
複製程式碼
不過也帶來2個問題:
1.用相對路徑代替數字,檔案變大了
2.相對路徑暴露了
HashedModuleIdsPlugin
:主要就是為了解決以上2個問題,它對相對路徑進行一個md5的摘要,不僅避免檔案過大,也隱藏了路徑。
Chunk ID
有時候一些模組可能在頁面初始化的時候並用不到,可能會在之後的過程中(比如使用者點選事件)才會用到,這一類模組可以通過動態import()來引入。
const helloWorld = () => {
import(`./sub`).then((module) => {
const sub = module.default;
sub();
})
};
複製程式碼
可以看到,動態引入的程式碼會被單獨打包到chunks/裡的檔案裡,而且vendor的值再次發生了改變。造成這個的原因是和moduleID類似,webpack預設使用整數作為chunkID,並且非同步載入的chunk會先被賦值。 也就是說在沒有動態引入之前,vendor的chunkID是0,動態引入之後,vendor檔案的chunkID變為了1,所以造成了內容變化。(上面提到的改變entry順序也會造成chunkID的變化)
NamedChunksPlugin:這個外掛會用chunk的字串name代替整數作為chunkID。不過要注意的是,動態生成的chunk並沒有名字,所以需要手動給取個名字。
new webpack.NamedChunksPlugin(function(chunk) {
if (chunk.name) {
return chunk.name;
}
return chunk.mapModules(function(m){ return m.id}).join("_");
})
複製程式碼
至此,可能造成hash值不穩定的元素都被幹掉。
Webpack4 更新
- CommonsChunksPlugin被幹掉了,解決runtime貌似只需要使用optimization.runtimeChunk就能搞定了
NamedModulesPlugin -> optimization.namedModules (on by default in develoment mode)
開發模式下預設會使用NamedModulesPlugin。
目測我們還是需要自己解決chunkID的問題,具體怎麼操作等下個月出了穩定版再學習吧。