摘要:前言如何基于做持久化緩存目前感覺是一直沒有一個非常好的方案來實踐。另外所有資源的值保持一致,這對于所有資源的持久化緩存來說并沒有深遠(yuǎn)的意義。到此持久化緩存中遇到的核心難題都已經(jīng)處理完了。
前言
如何基于webpack做持久化緩存目前感覺是一直沒有一個非常好的方案來實踐。網(wǎng)上的文章非常多,但是真的有用的非常少,并沒有一些真正深入研究和總結(jié)的文章。現(xiàn)在依托于于早教寶線上項目和自己的實踐,有了一個完整的方案。
正文1、webpack的hash的兩種計算方式
想要做持久化緩存那么就要依賴 webpack 自身提供的兩個 hash :hash和chunkhash。
接著就來看看這兩個值之間的具體含義和差別吧:
hash: webpack在每一次構(gòu)建的時候都會產(chǎn)生一個compilation對象,這個hash值就是根據(jù)compilation內(nèi)所有的內(nèi)容計算而來的值。
chunkhash:這個值是根據(jù)每個chunk的內(nèi)容而計算出來的值。
所以單純根據(jù)上面的描述來說,chunkhash是用來做持久化緩存最有效的。
2、hash和chunkhash的測試
entry | 入口文件 | 入口依賴 |
---|---|---|
pageA | a.js | a.less->a.css, common.js->common.css |
pageB | b.js | b.less->b.css, common.js->common.css |
使用hash計算
const path = require("path") const ExtractTextPlugin = require("extract-text-webpack-plugin") module.exports = { entry: { pageA: "./src/a.js", pageB: "./src/b.js" }, output: { filename: "[name]-[hash].js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ["css-loader?minimize"] }) } ] }, plugins: [new ExtractTextPlugin("[name]-[hash].css")] }
構(gòu)建結(jié)果
Hash: 80c922b349f516e79fb5 Version: webpack 3.8.1 Time: 1014ms Asset Size Chunks Chunk Names pageB-80c922b349f516e79fb5.js 2.86 kB 0 [emitted] pageB pageA-80c922b349f516e79fb5.js 2.84 kB 1 [emitted] pageA pageA-80c922b349f516e79fb5.css 21 bytes 1 [emitted] pageA pageB-80c922b349f516e79fb5.css 21 bytes 0 [emitted] pageB
結(jié)論
可以發(fā)現(xiàn)所有文件的hash全部都是一樣的,但是你多構(gòu)建幾次產(chǎn)生的hash都是不一樣的。原因在于我們使用了 ExtractTextPlugin,ExtractTextPlugin 本身涉及到異步的抽取流程,所以在生成 assets 資源時存在了不確定性(先后順序),而 updateHash 則對其敏感,所以就出現(xiàn)了如上所說的 hash 異動的情況。另外所有 assets 資源的 hash 值保持一致,這對于所有資源的持久化緩存來說并沒有深遠(yuǎn)的意義。
使用chunkhash計算
const path = require("path") const ExtractTextPlugin = require("extract-text-webpack-plugin") module.exports = { entry: { pageA: "./src/a.js", pageB: "./src/b.js" }, output: { filename: "[name]-[chunkhash].js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ["css-loader?minimize"] }) } ] }, plugins: [new ExtractTextPlugin("[name]-[chunkhash].css")] }
構(gòu)建結(jié)果
Hash: 810904f973cc0cf41992 Version: webpack 3.8.1 Time: 1038ms Asset Size Chunks Chunk Names pageB-e9ed5150262ba39827d4.js 2.86 kB 0 [emitted] pageB pageA-3a2e5ef3d4506fce8d93.js 2.84 kB 1 [emitted] pageA pageA-3a2e5ef3d4506fce8d93.css 21 bytes 1 [emitted] pageA pageB-e9ed5150262ba39827d4.css 21 bytes 0 [emitted] pageB
結(jié)論
此時可以發(fā)現(xiàn),運(yùn)行多少次,hash 的變動沒有了,每個 entry 擁有了自己獨(dú)一的 hash 值,細(xì)心的你或許會發(fā)現(xiàn)此時樣式資源的 hash 值和 入口腳本保持了一致,這似乎并不符合我們的想法,冥冥之中告訴我們發(fā)生了某些壞事情。
3、探索css文件的hash和入口文件hash之間的關(guān)系
在上面的構(gòu)建結(jié)果中,我們發(fā)現(xiàn)css的hash值和入口文件的hash值是一樣的,這里我們?nèi)菀桩a(chǎn)生疑問,是不是這兩個文件之間一定會有聯(lián)系呢?呆著疑問去修改下b.css文件中的內(nèi)容,產(chǎn)生構(gòu)建結(jié)果:
Hash: 3d95035f096f3ca08761 Version: webpack 3.8.1 Time: 1028ms Asset Size Chunks Chunk Names pageB-e9ed5150262ba39827d4.js 2.86 kB 0 [emitted] pageB pageA-3a2e5ef3d4506fce8d93.js 2.84 kB 1 [emitted] pageA pageA-3a2e5ef3d4506fce8d93.css 21 bytes 1 [emitted] pageA pageB-e9ed5150262ba39827d4.css 41 bytes 0 [emitted] pageB
納尼???改動css文件內(nèi)容,為什么css文件的hash沒有改變呢?不科學(xué)啊,入口文件的hash也沒有改變。仔細(xì)想了一下 webpack 是將所有的內(nèi)容都認(rèn)為是js文件的一部分。在構(gòu)建的過程中使用 ExtractTextPlugin 將樣式抽離出entry chunk 了,而此時的 entry chunk 本身并沒有發(fā)生改變,改變的是已經(jīng)被抽離出去的css部分。而chunkunhash 卻是根據(jù) chunk 計算出來的,所以不變更應(yīng)該是正常的。但是這個又不符合我們想要做的持久化緩存的要求,因為又變動就應(yīng)該改變hash才是。
開心的是 ExtractTextPlugin 插件為我們提供了一個contenthash來變化:
plugins: [new ExtractTextPlugin("[name]-[contenthash].css")]
修改b.css前后兩次構(gòu)建結(jié)果:
Hash: 3d95035f096f3ca08761 Version: webpack 3.8.1 Time: 1091ms Asset Size Chunks Chunk Names pageB-e9ed5150262ba39827d4.js 2.86 kB 0 [emitted] pageB pageA-3a2e5ef3d4506fce8d93.js 2.84 kB 1 [emitted] pageA pageA-9783744431577cdcfea658734b7db20f.css 21 bytes 1 [emitted] pageA pageB-2d03aa12ae45c64dedd7f66bb88dd3db.css 41 bytes 0 [emitted] pageB
Hash: 7a96bcf1ef668a49c9d8 Version: webpack 3.8.1 Time: 1193ms Asset Size Chunks Chunk Names pageB-e9ed5150262ba39827d4.js 2.86 kB 0 [emitted] pageB pageA-3a2e5ef3d4506fce8d93.js 2.84 kB 1 [emitted] pageA pageA-9783744431577cdcfea658734b7db20f.css 21 bytes 1 [emitted] pageA pageB-7e05e00e24f795b674df5701f6a38bd9.css 42 bytes 0 [emitted] pageB
對比發(fā)現(xiàn)修改了樣式文件后只有樣式文件的hash發(fā)生了改變,符合我們想要的預(yù)期。
4、module id的不可控和修正
經(jīng)過上面的測試,我們理所當(dāng)然的認(rèn)為我完成了持久化緩存的hash穩(wěn)定。然后我們不小心刪除了a.js中的a.less文件,然后前后兩次構(gòu)建:
Hash: 88ab71080c53db9d9f70 Version: webpack 3.8.1 Time: 1279ms Asset Size Chunks Chunk Names pageB-a2d1e1d73336f17e2dc4.js 3.82 kB 0 [emitted] pageB pageA-96c9f5afea30e7e09628.js 3.8 kB 1 [emitted] pageA pageA-d7ac82de795ddf50c9df43291d77b4c8.css 92 bytes 1 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 0 [emitted] pageB
Hash: 172153ea2b39c2046a92 Version: webpack 3.8.1 Time: 1260ms Asset Size Chunks Chunk Names pageB-884da67fe2322246ab28.js 3.81 kB 0 [emitted] pageB pageA-4c0dfb634722c556ffa0.js 3.68 kB 1 [emitted] pageA pageA-35be2c21107ce4016c324daaa1dd5e28.css 49 bytes 1 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 0 [emitted] pageB
奇怪的事產(chǎn)生了,我移除了a.less文件后發(fā)現(xiàn)pageB入口文件的hash都改變了。如果只有pageA相關(guān)的文件hash變了我還可以理解。但是????為什么都變了???不行我得看看為什么都變了。
通過上面的diff發(fā)現(xiàn)我們移除了a.less后整體的id發(fā)生了改變了。那么這個地方的id我們可以推測是代表的是具體的引用的模塊。
接著我們在看看前后兩次構(gòu)建模塊的信息:
[3] ./src/a.js 284 bytes {1} [built] [4] ./src/a.less 41 bytes {1} [built] [5] ./src/b.js 284 bytes {0} [built] [6] ./src/b.less 41 bytes {0} [built]
[3] ./src/a.js 264 bytes {1} [built] [4] ./src/b.js 284 bytes {0} [built] [5] ./src/b.less 41 bytes {0} [built]
通過對比發(fā)現(xiàn)前面的序號在構(gòu)建出來的pageB中有隱藏pageA相關(guān)的信息,這對于我們來做持久化緩存來說是非常不便的。我們期待的是pageB中只包含和自身相關(guān)的信息,不包含其他與自身無關(guān)的信息。
5、module id的變化
排除與己不相關(guān)的module id或者內(nèi)容
會用webpack的人大概都之都一個特性:Code Splitting,本質(zhì)上是對 chunk 進(jìn)行拆分再組合的過程。具體要怎么做呢?
The answer is CommonsChunkPlugin,在plugin中添加:
plugins: [ new ExtractTextPlugin("[name]-[contenthash].css"), new webpack.optimize.CommonsChunkPlugin({ name: "runtime" }) ]
接下來在看看移除pageA中的a.less的前后變化:
Hash: 697b36118920d991364a Version: webpack 3.8.1 Time: 1488ms Asset Size Chunks Chunk Names pageB-9b2eb6768499c911a728.js 491 bytes 0 [emitted] pageB pageA-c342383ca09604e8e7b8.js 495 bytes 1 [emitted] pageA runtime-b6ec3c0d350aef6cbf3e.js 6.8 kB 2 [emitted] runtime pageA-b812cf5b72744af29181f642fe4dbf38.css 43 bytes 1 [emitted] pageA pageB-af8f1e92fd031bd1d1d8db5390b5d0d5.css 59 bytes 0 [emitted] pageB runtime-35be2c21107ce4016c324daaa1dd5e28.css 49 bytes 2 [emitted] runtime
Hash: 7ddaf109d5aa67c43ce2 Version: webpack 3.8.1 Time: 1793ms Asset Size Chunks Chunk Names pageB-613cc5a6a90adfb635f4.js 491 bytes 0 [emitted] pageB pageA-0b72f85fda69a9442076.js 375 bytes 1 [emitted] pageA runtime-a41b8b8bfe7ec70fd058.js 6.79 kB 2 [emitted] runtime pageB-af8f1e92fd031bd1d1d8db5390b5d0d5.css 59 bytes 0 [emitted] pageB runtime-35be2c21107ce4016c324daaa1dd5e28.css 49 bytes 2 [emitted] runtime
接著在看看兩次構(gòu)建中pageB的對比:
經(jīng)過對比我們發(fā)現(xiàn)在pageB中只包含的是自身相關(guān)的內(nèi)容。所以使用CommonsChunkPlugin達(dá)到了我們的期望。而抽離出去的代碼就是webpack的運(yùn)行時代碼。運(yùn)行時代碼也存儲著webpack對module和chunk相關(guān)的信息。另外我們發(fā)現(xiàn)pageA和pageB的文件大小也發(fā)生了變化。導(dǎo)致這個變化的原因是CommonsChunkPlugin會默認(rèn)的把entry chunk都包含的module抽取到我們?nèi)∶麨閞untime的normal chunk中去。
假如我們在開發(fā)中每個頁面都會用到一些工具庫,例如lodash這類的。由于CommonsChunkPlugin的默認(rèn)行為會抽取公共部分,可能lodash并沒有發(fā)生改變,但是被抽離在運(yùn)行時代碼中的時候,每次都是會去請求新的。這不能達(dá)到我們要求的最小更新原則。所以我們要人工去干預(yù)一些代碼。
plugins: [ new ExtractTextPlugin("[name]-[contenthash].css"), new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ name: "runtime" })
在次對邊前后兩次構(gòu)建的日志:
Hash: a703a57c828ec32b24e1 Version: webpack 3.8.1 Time: 1493ms Asset Size Chunks Chunk Names vendor-f11f58b8150930590a10.js 541 kB 0 [emitted] [big] vendor pageB-7d065cd319176f44c605.js 938 bytes 1 [emitted] pageB pageA-2b7e3707314e7ec4d770.js 910 bytes 2 [emitted] pageA runtime-e68dec8bcad8a5870f0c.js 5.88 kB 3 [emitted] runtime pageA-d7ac82de795ddf50c9df43291d77b4c8.css 92 bytes 2 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 1 [emitted] pageB
Hash: 26fc9ad18554b28cd8e1 Version: webpack 3.8.1 Time: 1806ms Asset Size Chunks Chunk Names vendor-d9bad56677b04b803651.js 541 kB 0 [emitted] [big] vendor pageB-a55dadfbf25a45856d6a.js 929 bytes 1 [emitted] pageB pageA-7cbd77a502262ddcdd19.js 790 bytes 2 [emitted] pageA runtime-fa8eba6e81ed41f50d6f.js 5.88 kB 3 [emitted] runtime pageA-35be2c21107ce4016c324daaa1dd5e28.css 49 bytes 2 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 1 [emitted] pageB
到此為止我們解決了:排除與己不相關(guān)的module id或者內(nèi)容問題。
穩(wěn)定module id,盡可能的保持module id保持不變
一個module id是一個模塊的唯一標(biāo)示,并且該標(biāo)示會出現(xiàn)在對應(yīng)的entry chunk構(gòu)建后的代碼中。看個pageB的構(gòu)建后代碼的例子:
__webpack_require__(7) const sum = __webpack_require__(0) const _ = __webpack_require__(3)
根據(jù)前面的實驗,模塊的增加或者減少都會引起module id的改變,所以為了不引起module id的改變,那么我們只能找一個東西來代替module id作為標(biāo)示。我們在構(gòu)建的過程中就將尋找出來替代標(biāo)示來替換module id。
所以上面的敘述可以轉(zhuǎn)換成兩個步驟來行動。
找到替代module id的方式
找到時機(jī)替換module id
6、穩(wěn)定 module id 的相關(guān)操作
找到替代module id的方式
我們在日常的開發(fā)中,經(jīng)常引用模塊,都是通過地址來引用的。從這里我們可以得到啟發(fā),我們能不能夠把module id全部替換成路徑呢?再一個我們了解到在webpack resolve module階段我們肯定是可以拿到資源路徑的。在開始我們擔(dān)心平臺的路徑差異性。幸運(yùn)的是webpack 的源碼其中在 ContextModule#74 和 ContextModule#35 中 webpack 對 module 的路徑做了差異性修復(fù)。也就是說我們可以放心的通過module的libIdent來獲取模塊的路徑了。
在整個webpack的執(zhí)行過程中涉及到module id有三個鉤子:
before-module-ids -> optimize-module-ids -> after-optimize-module-ids
所以我們只要在before-module-ids中做出修改就好了。
編寫插件:
"use strict" class moduleIDsByFilePath { constructor(options) {} apply(compiler) { compiler.plugin("compilation", compilation => { compilation.plugin("before-module-ids", (modules) => { modules.forEach((module) => { if(module.id === null && module.libIdent) { module.id = module.libIdent({ context: this.options.context || compiler.options.context }) } }) }) }) } } module.exports = moduleIDsByFilePath
上面的其實已經(jīng)被webpack抽成一個插件了:
NamedModulesPlugin
所以只需要在插件那一部分里面添加上
new webpack.NamedModulesPlugin()
接下來對比下兩次構(gòu)建前后文件的變化:
Hash: e5bc78237ca9a3ad31f8 Version: webpack 3.8.1 Time: 1508ms Asset Size Chunks Chunk Names vendor-ebd9bfc583f45a344630.js 541 kB 0 [emitted] [big] vendor pageB-432105effc229524c683.js 1.09 kB 1 [emitted] pageB pageA-158bf2a923c98ab49be2.js 1.09 kB 2 [emitted] pageA runtime-9ca4cebe90e444e723b9.js 5.88 kB 3 [emitted] runtime pageA-d7ac82de795ddf50c9df43291d77b4c8.css 92 bytes 2 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 1 [emitted] pageB
Hash: 7dce5d9dc88f619522fe Version: webpack 3.8.1 Time: 1422ms Asset Size Chunks Chunk Names vendor-ebd9bfc583f45a344630.js 541 kB 0 [emitted] [big] vendor pageB-432105effc229524c683.js 1.09 kB 1 [emitted] pageB pageA-dae883ddaeff861761da.js 940 bytes 2 [emitted] pageA runtime-c874a0c304fa03493296.js 5.88 kB 3 [emitted] runtime pageA-35be2c21107ce4016c324daaa1dd5e28.css 49 bytes 2 [emitted] pageA pageB-56185455ea60f01155a65497e9bf6c85.css 108 bytes 1 [emitted] pageB
哇,我們對比發(fā)現(xiàn)只有相關(guān)改動的文件和運(yùn)行時代碼發(fā)生了改變,vendor和pageB相關(guān)都沒有發(fā)生改變。美滋滋~~
這下我們達(dá)到了我們的目的,我們可以去看看我們構(gòu)建后的代碼了:
__webpack_require__("./src/b.less") const sum = __webpack_require__("./src/common.js") const _ = __webpack_require__("./node_modules/lodash/lodash.js")
真的是變成了路徑,成功~~。但是新的問題貌似又來了,和之前的文件對比發(fā)現(xiàn)我們的文件普遍比之前的變大了。好吧,是我們換成文件路徑的時候造成的。這個時候我們能不能用hash來代替文件路徑呢?答案是可以,官方也有插件可以供我們使用:
new webpack.HashedModuleIdsPlugin()
官方說 NamedModulesPlugin 適合在開發(fā)環(huán)境,而在生產(chǎn)環(huán)境下請使用 HashedModuleIdsPlugin。
這樣我們就達(dá)成了使用hash來代替原來的module id使之穩(wěn)定。而且構(gòu)建后的代碼也不會變化太大。
本以為可以到此為止了。但是細(xì)心的人會發(fā)現(xiàn)runtime文件每次編譯都發(fā)生了變化。是什么導(dǎo)致呢的?來看看吧:
我們觀察發(fā)現(xiàn),在我們的entry chunk數(shù)量沒有發(fā)生變化的時候,改變一個entry chunk的內(nèi)容導(dǎo)致runtime內(nèi)容發(fā)生變化的只有chunk id這個時候問題就又來了。根據(jù)上面穩(wěn)定module id的操作一樣,數(shù)值型的chunk id不穩(wěn)定性太大,我們要換,方式和上面一樣。
找到穩(wěn)定chunk id的方式
找到改變chunk id的時機(jī)
7、穩(wěn)定chunk id的相關(guān)操作
找到穩(wěn)定chunk id的方式
因為我們知道webpack在打包的時候入口是具有唯一性的,那么很簡單我們能不能夠用入口對應(yīng)的name呢?所以這里就比較簡單了我們就用我們的entry name來替換chunk id。
找到改變chunk id的時機(jī)
根據(jù)經(jīng)驗module 有上面的過程那么 chunk我覺得也是有的。
before-chunk-ids -> optimize-chunk-ids -> after-optimize-chunk-ids
所以編寫插件:
"use strict" class chunkIDsByFilePath { constructor(options) {} apply(compiler) { compiler.plugin("compilation", compilation => { compilation.plugin("before-chunk-ids", chunks => { chunks.forEach(chunk => { chunk.id = chunk.name }) }) }) } } module.exports = chunkIDsByFilePath
不巧的是官方也有這個插件所以不用我們寫。
NamedChunksPlugin
構(gòu)建后的代碼里面我們可以看到了:
/******/ script.src = __webpack_require__.p + "" + chunkId + "-" + {"vendor":"ed00d7222262ac99e510","pageA":"b5b4e2893bce99fd5c57","pageB":"34be879b3374ac9b2072"}[chunkId] + ".js";
原來的chunk id現(xiàn)在全部變成了entry name了,變更的風(fēng)險又小了一點(diǎn)了。美滋滋~~
我們換成名字后那么問題又和上面module id換成name 又一樣的問題,文件會變大。這個時候還是想到和上面的方式一樣用hash來處理。這個時候就真的要編寫插件了。安利一波我們自己寫的
webpack-hashed-chunk-id-plugin。
到此持久化緩存中遇到的核心難題都已經(jīng)處理完了。
最后如果你想要快速搭建一個項目,歡迎使用這邊的項目架構(gòu)哦。
webpack-project-seed已經(jīng)有線上項目用的用這個在跑了哦。順便star一個吧。
感謝:@pigcan
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90030.html
摘要:本期大綱隨著從到千萬用戶的業(yè)務(wù)增長,通過的不同服務(wù)輕松地實現(xiàn)高性能和高可用的基礎(chǔ)架構(gòu)。方坤老師本次的主題比較偏向?qū)嵺`的基礎(chǔ)部分,假設(shè)了一個應(yīng)用從小型到中型和大型的時候,可能需要用到的服務(wù),以及相關(guān)介紹和實踐建議。 極牛技術(shù)實踐分享活動 極牛技術(shù)實踐分享系列活動是極牛聯(lián)合頂級VC、技術(shù)專家,為企業(yè)、技術(shù)人提供的一種系統(tǒng)的線上技術(shù)分享活動。每期不同的技術(shù)主題,和行業(yè)專家深度探討,專注...
摘要:和是新加的,是對原狀態(tài)碼的細(xì)化。規(guī)定處理應(yīng)是重定向為,處理應(yīng)該是重定向為不一定是非請求即可和的存在,歸根結(jié)底是由于方法的非冪等屬性引起的。所以同時存在時,只有生效。超過該數(shù)值會有累積與端口耗盡問題。 前言 本文梳理本人閱讀《HTTP權(quán)威指南》遇到的相關(guān)問題與相關(guān)解答。若有錯誤請指正。 OSI參考模型 應(yīng)用層,表示層,會話層,傳輸層,網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層,物理層 URL ://:@:/;?...
摘要:本篇不包含所有坑,暫時只記錄自己踩到的部分坑一安裝安裝最新版本安裝新增依賴這個在中,本身和它的是在同一個包中,中將兩個分開管理。我記錄下自己更新這個的過程,以下前半部分可以直接跳過。以下記錄踩坑過程。 本篇不包含所有坑,暫時只記錄自己踩到的部分坑 一.安裝 安裝webpack4最新版本 npm install --save-dev webpack@4 安裝新增依賴 webpack-c...
閱讀 2161·2021-11-15 11:36
閱讀 1496·2021-09-23 11:55
閱讀 2494·2021-09-22 15:16
閱讀 2032·2019-08-30 15:45
閱讀 1868·2019-08-29 11:10
閱讀 1032·2019-08-26 13:40
閱讀 921·2019-08-26 10:44
閱讀 3173·2019-08-23 14:55