前幾天廠里的網突然渣掉了,某些網頁變得極度卡頓,但是劃水網站依舊流暢;我覺得很有趣就打開 DevTools 對比了一下,結果看到某卡頓網頁的 Network 狀況如下,我大概猜到了一些緣由。這期就借機講講前端部署里的一些小技巧吧。
原始前端
OK,這期討論的原始前端當然也沒那么“原始”,我無意追述到 JSP,thymeleaf 這類傳統后端渲染的技術。只是說“原始前端”開發比較簡單,只需要一頁文本編輯器,一份 html 模版和一款打包工具(webpack);部署更簡單,webpack 構建并打包出 html、css、js,然后一股腦丟到服務器上就行。當請求到來時,服務器返回 index.html, 然后再把它的相關引用(js、css)返回給客戶端即可。
但是這里有個問題是,引用文件名——onion.js——是固定的:一旦部署更新后,就得防止客戶端緩存舊文件,所以一般會設置Cache-Control: no-cache
。這就是文章開頭提到的一個問題,每次打開頁面都得重新加載一遍引用文件,一旦網絡渣渣了,首頁渲染就會十分緩慢。
版本管理
看樣子緩存策略還是需要保留的,可如何兼顧更新呢?有人就想到了給引用路勁加個版本號:
p.s. index.html 始終是 no-cache
新版本上線后,瀏覽器請求 html,發現資源路勁更改了,便主動放棄相關緩存,重新加載引用文件。這個小小的改變很實用,兼顧了緩存策略和新舊版本文件沖突。至于這個版本號怎么添加,各色打包工具都能實現,最簡單的策略就是在 index.html 模版里加個時間戳。
摘要算法
當然,隨著業務增長,js 和 css 文件會變得愈發巨大,僅僅加載單個文件就可能需要數秒。遇到問題就解決問題,把大文件拆成小份就行了!利用瀏覽器異步加載多個文件的機制,拆分 JS 能迅速提速。(當然,瀏覽器并發加載數有限,拆太細會適得其反)。
這時候有聰明人就發現了:每次更新時,某些 js 文件并沒有被修改,為什么也要增加版本號呢?讓客戶無端加載一個一模一樣的 js 文件,不是很浪費嗎?
反正版本號就是一個 unique 值——防沖突用的,不如換成別的數值吧?比如,經典的MD5(消息摘要)算法生成的content hash值,該哈希值與文件內容一一對應的,這就有了精確到單個文件的版本控制信息了。
我自己開發時,會利用 webpack 的 splitChunks 功能,把 Vue 全家桶打包成單個 js 文件、UI 框架也打包成單個 js;這種文件體積不小,但基本不會變動,cache-control/max-age
可以寫大一些,客戶端除了首次訪問會慢一點,其他時候基本就disk cache
了。
CDN
上面提到服務器存放靜態資源文件,但這種形式在性能上也有缺陷:
- 受地理環境影響,離服務器越遠資源加載越慢
- 頻繁請求資源對服務器壓力較大
為了進一步提升性能,大家開始把動態網頁(index.html)和靜態資源(js、css、image...)分開部署。靜態資源被放置于 CDN 上。
但是 CDN 也有緩存策略:新的靜態資源發布后,需要一定的時間去覆蓋各個邊緣站點的舊資源。若某客戶端獲得了新的動態網頁,但是附近的 CDN 節點尚未更新最近發布的靜態資源,客戶端即便放棄本地緩存,它加載的依舊是位于 CDN 上的“臟數據”。怎么辦呢?干脆把文件名也給改了——讓摘要信息成為文件名的一部分!具體實現還是可以仰仗 webpack,將 output.filename
設為 [name].[contenthash].js
,輸出文件和 html 模版都會幫你更改好。
用摘要信息重命名后的資源文件,與舊資源就不同名了,不再需要以覆蓋舊文件的形式主動更新各個地區的邊緣站點。新版本發布后,瀏覽器首次請求資源,若 CDN 不存在該資源,便會向就近的邊緣站點加載該文件,同時更新 CDN 緩存;這就徹底避免了 CDN 臟數據的問題。
Build
關于構建,我自己所在項目組的做法是前端單獨部署:git 接收 MR 后觸發 AWS 的 code pipeline 和 code build;Webpack 自動打包生成dist
文件夾(里面包含 html、js、css 等資源),html 部署到服務器上,其他靜態資源發布到 CDN 上。這一套下來也不貴:一個月十幾刀,每次部署也就幾分鐘。但是,但是!我后來看到有些廠的做法,覺得實在是太“摳”了——成本控制得真好!
他們的做法是這樣的:build 在開發環境就完成了,然后把打包生成的dist
文件夾也 push 到 git 上。好處很明顯:
- 節省了一套 code pipeline + code build 的花費:大公司就不是一個月十幾刀的事了
- 大大縮短 build 時間:構建環境是不包含
node_modules
的,大型項目光npm install
就要很久,build 時間更是感人
而開發人員本地 build,可以很完美地繞開上面兩個缺點。配置上寫個 hook,在git commit
時多加如下三個 changes 即可:
modified: dist/index.html
delete: dist/garlic.6b58.js
new file: dist/garlic.e5i1.js
最后的犧牲只是 git repo 上包含了一個 dist 文件夾而已,真的是很“摳門”啊。
小結
這期內容很簡單,講了講靜態資源文件名的那些事,算是螺螄殼里做道場吧。一直在想一件事,我們追求新技術的本質是什么?我覺得是降低成本!軟件本質上還是屬于“工業產品”。新聞上能看到一些重工業單位通過調整工藝大大降低成本的案例;我們做軟件的也應該不斷調整工藝,降低成本、減少設備消耗,從而幫助公司賺取更多的利潤。我曾看過有人形容軟件開發就是一種緩慢地破壞+重建的過程,這種觀點還是挺有道理的,分享給大家。