摘要:前言如何基于做持久化緩存目前感覺是一直沒有一個非常好的方案來實踐。另外所有資源的值保持一致,這對于所有資源的持久化緩存來說并沒有深遠的意義。到此持久化緩存中遇到的核心難題都已經處理完了。
前言
如何基于webpack做持久化緩存目前感覺是一直沒有一個非常好的方案來實踐。網上的文章非常多,但是真的有用的非常少,并沒有一些真正深入研究和總結的文章。現在依托于于早教寶線上項目和自己的實踐,有了一個完整的方案。
正文1、webpack的hash的兩種計算方式
想要做持久化緩存那么就要依賴 webpack 自身提供的兩個 hash :hash和chunkhash。
接著就來看看這兩個值之間的具體含義和差別吧:
hash: webpack在每一次構建的時候都會產生一個compilation對象,這個hash值就是根據compilation內所有的內容計算而來的值。
chunkhash:這個值是根據每個chunk的內容而計算出來的值。
所以單純根據上面的描述來說,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")] }
構建結果
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
結論
可以發現所有文件的hash全部都是一樣的,但是你多構建幾次產生的hash都是不一樣的。原因在于我們使用了 ExtractTextPlugin,ExtractTextPlugin 本身涉及到異步的抽取流程,所以在生成 assets 資源時存在了不確定性(先后順序),而 updateHash 則對其敏感,所以就出現了如上所說的 hash 異動的情況。另外所有 assets 資源的 hash 值保持一致,這對于所有資源的持久化緩存來說并沒有深遠的意義。
使用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")] }
構建結果
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
結論
此時可以發現,運行多少次,hash 的變動沒有了,每個 entry 擁有了自己獨一的 hash 值,細心的你或許會發現此時樣式資源的 hash 值和 入口腳本保持了一致,這似乎并不符合我們的想法,冥冥之中告訴我們發生了某些壞事情。
3、探索css文件的hash和入口文件hash之間的關系
在上面的構建結果中,我們發現css的hash值和入口文件的hash值是一樣的,這里我們容易產生疑問,是不是這兩個文件之間一定會有聯系呢?呆著疑問去修改下b.css文件中的內容,產生構建結果:
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文件內容,為什么css文件的hash沒有改變呢?不科學啊,入口文件的hash也沒有改變。仔細想了一下 webpack 是將所有的內容都認為是js文件的一部分。在構建的過程中使用 ExtractTextPlugin 將樣式抽離出entry chunk 了,而此時的 entry chunk 本身并沒有發生改變,改變的是已經被抽離出去的css部分。而chunkunhash 卻是根據 chunk 計算出來的,所以不變更應該是正常的。但是這個又不符合我們想要做的持久化緩存的要求,因為又變動就應該改變hash才是。
開心的是 ExtractTextPlugin 插件為我們提供了一個contenthash來變化:
plugins: [new ExtractTextPlugin("[name]-[contenthash].css")]
修改b.css前后兩次構建結果:
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
對比發現修改了樣式文件后只有樣式文件的hash發生了改變,符合我們想要的預期。
4、module id的不可控和修正
經過上面的測試,我們理所當然的認為我完成了持久化緩存的hash穩定。然后我們不小心刪除了a.js中的a.less文件,然后前后兩次構建:
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
奇怪的事產生了,我移除了a.less文件后發現pageB入口文件的hash都改變了。如果只有pageA相關的文件hash變了我還可以理解。但是????為什么都變了???不行我得看看為什么都變了。
通過上面的diff發現我們移除了a.less后整體的id發生了改變了。那么這個地方的id我們可以推測是代表的是具體的引用的模塊。
接著我們在看看前后兩次構建模塊的信息:
[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]
通過對比發現前面的序號在構建出來的pageB中有隱藏pageA相關的信息,這對于我們來做持久化緩存來說是非常不便的。我們期待的是pageB中只包含和自身相關的信息,不包含其他與自身無關的信息。
5、module id的變化
排除與己不相關的module id或者內容
會用webpack的人大概都之都一個特性:Code Splitting,本質上是對 chunk 進行拆分再組合的過程。具體要怎么做呢?
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
接著在看看兩次構建中pageB的對比:
經過對比我們發現在pageB中只包含的是自身相關的內容。所以使用CommonsChunkPlugin達到了我們的期望。而抽離出去的代碼就是webpack的運行時代碼。運行時代碼也存儲著webpack對module和chunk相關的信息。另外我們發現pageA和pageB的文件大小也發生了變化。導致這個變化的原因是CommonsChunkPlugin會默認的把entry chunk都包含的module抽取到我們取名為runtime的normal chunk中去。
假如我們在開發中每個頁面都會用到一些工具庫,例如lodash這類的。由于CommonsChunkPlugin的默認行為會抽取公共部分,可能lodash并沒有發生改變,但是被抽離在運行時代碼中的時候,每次都是會去請求新的。這不能達到我們要求的最小更新原則。所以我們要人工去干預一些代碼。
plugins: [ new ExtractTextPlugin("[name]-[contenthash].css"), new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ name: "runtime" })
在次對邊前后兩次構建的日志:
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
到此為止我們解決了:排除與己不相關的module id或者內容問題。
穩定module id,盡可能的保持module id保持不變
一個module id是一個模塊的唯一標示,并且該標示會出現在對應的entry chunk構建后的代碼中。看個pageB的構建后代碼的例子:
__webpack_require__(7) const sum = __webpack_require__(0) const _ = __webpack_require__(3)
根據前面的實驗,模塊的增加或者減少都會引起module id的改變,所以為了不引起module id的改變,那么我們只能找一個東西來代替module id作為標示。我們在構建的過程中就將尋找出來替代標示來替換module id。
所以上面的敘述可以轉換成兩個步驟來行動。
找到替代module id的方式
找到時機替換module id
6、穩定 module id 的相關操作
找到替代module id的方式
我們在日常的開發中,經常引用模塊,都是通過地址來引用的。從這里我們可以得到啟發,我們能不能夠把module id全部替換成路徑呢?再一個我們了解到在webpack resolve module階段我們肯定是可以拿到資源路徑的。在開始我們擔心平臺的路徑差異性。幸運的是webpack 的源碼其中在 ContextModule#74 和 ContextModule#35 中 webpack 對 module 的路徑做了差異性修復。也就是說我們可以放心的通過module的libIdent來獲取模塊的路徑了。
在整個webpack的執行過程中涉及到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
上面的其實已經被webpack抽成一個插件了:
NamedModulesPlugin
所以只需要在插件那一部分里面添加上
new webpack.NamedModulesPlugin()
接下來對比下兩次構建前后文件的變化:
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
哇,我們對比發現只有相關改動的文件和運行時代碼發生了改變,vendor和pageB相關都沒有發生改變。美滋滋~~
這下我們達到了我們的目的,我們可以去看看我們構建后的代碼了:
__webpack_require__("./src/b.less") const sum = __webpack_require__("./src/common.js") const _ = __webpack_require__("./node_modules/lodash/lodash.js")
真的是變成了路徑,成功~~。但是新的問題貌似又來了,和之前的文件對比發現我們的文件普遍比之前的變大了。好吧,是我們換成文件路徑的時候造成的。這個時候我們能不能用hash來代替文件路徑呢?答案是可以,官方也有插件可以供我們使用:
new webpack.HashedModuleIdsPlugin()
官方說 NamedModulesPlugin 適合在開發環境,而在生產環境下請使用 HashedModuleIdsPlugin。
這樣我們就達成了使用hash來代替原來的module id使之穩定。而且構建后的代碼也不會變化太大。
本以為可以到此為止了。但是細心的人會發現runtime文件每次編譯都發生了變化。是什么導致呢的?來看看吧:
我們觀察發現,在我們的entry chunk數量沒有發生變化的時候,改變一個entry chunk的內容導致runtime內容發生變化的只有chunk id這個時候問題就又來了。根據上面穩定module id的操作一樣,數值型的chunk id不穩定性太大,我們要換,方式和上面一樣。
找到穩定chunk id的方式
找到改變chunk id的時機
7、穩定chunk id的相關操作
找到穩定chunk id的方式
因為我們知道webpack在打包的時候入口是具有唯一性的,那么很簡單我們能不能夠用入口對應的name呢?所以這里就比較簡單了我們就用我們的entry name來替換chunk id。
找到改變chunk id的時機
根據經驗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
構建后的代碼里面我們可以看到了:
/******/ script.src = __webpack_require__.p + "" + chunkId + "-" + {"vendor":"ed00d7222262ac99e510","pageA":"b5b4e2893bce99fd5c57","pageB":"34be879b3374ac9b2072"}[chunkId] + ".js";
原來的chunk id現在全部變成了entry name了,變更的風險又小了一點了。美滋滋~~
我們換成名字后那么問題又和上面module id換成name 又一樣的問題,文件會變大。這個時候還是想到和上面的方式一樣用hash來處理。這個時候就真的要編寫插件了。安利一波我們自己寫的
webpack-hashed-chunk-id-plugin。
到此持久化緩存中遇到的核心難題都已經處理完了。
最后如果你想要快速搭建一個項目,歡迎使用這邊的項目架構哦。
webpack-project-seed已經有線上項目用的用這個在跑了哦。順便star一個吧。
感謝:@pigcan
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90030.html
摘要:本期大綱隨著從到千萬用戶的業務增長,通過的不同服務輕松地實現高性能和高可用的基礎架構。方坤老師本次的主題比較偏向實踐的基礎部分,假設了一個應用從小型到中型和大型的時候,可能需要用到的服務,以及相關介紹和實踐建議。 極牛技術實踐分享活動 極牛技術實踐分享系列活動是極牛聯合頂級VC、技術專家,為企業、技術人提供的一種系統的線上技術分享活動。每期不同的技術主題,和行業專家深度探討,專注...
摘要:和是新加的,是對原狀態碼的細化。規定處理應是重定向為,處理應該是重定向為不一定是非請求即可和的存在,歸根結底是由于方法的非冪等屬性引起的。所以同時存在時,只有生效。超過該數值會有累積與端口耗盡問題。 前言 本文梳理本人閱讀《HTTP權威指南》遇到的相關問題與相關解答。若有錯誤請指正。 OSI參考模型 應用層,表示層,會話層,傳輸層,網絡層,數據鏈路層,物理層 URL ://:@:/;?...
摘要:本篇不包含所有坑,暫時只記錄自己踩到的部分坑一安裝安裝最新版本安裝新增依賴這個在中,本身和它的是在同一個包中,中將兩個分開管理。我記錄下自己更新這個的過程,以下前半部分可以直接跳過。以下記錄踩坑過程。 本篇不包含所有坑,暫時只記錄自己踩到的部分坑 一.安裝 安裝webpack4最新版本 npm install --save-dev webpack@4 安裝新增依賴 webpack-c...
閱讀 2158·2021-11-15 11:36
閱讀 1485·2021-09-23 11:55
閱讀 2493·2021-09-22 15:16
閱讀 2030·2019-08-30 15:45
閱讀 1867·2019-08-29 11:10
閱讀 1032·2019-08-26 13:40
閱讀 919·2019-08-26 10:44
閱讀 3173·2019-08-23 14:55