2021 年當(dāng)我們聊前端部署時揉燃,我們在聊什么

原文轉(zhuǎn)載來自 字節(jié)架構(gòu)前端

先上靈魂拷問

在文章之前蒸辆,先拋一些靈魂拷問:

  • 前端代碼從 tsx/jsx 到部署上線被用戶訪問跟磨,中間大致會經(jīng)歷哪些過程定罢?

  • 上述過程中分別都有哪些考慮糠溜、指標(biāo)和優(yōu)化點(diǎn)淳玩,以滿足復(fù)雜的業(yè)務(wù)需求?

  • 可能大部分同學(xué)都知道強(qiáng)緩存/協(xié)商緩存非竿,那前端各種產(chǎn)物(HTML蜕着、JS、CSS红柱、IMAGES 等)應(yīng)該用什么緩存策略承匣?以及為什么?

  • 若使用協(xié)商緩存锤悄,但靜態(tài)資源卻不頻繁更新韧骗,如何避免協(xié)商過程的請求浪費(fèi)?

  • 若使用強(qiáng)緩存零聚,那靜態(tài)資源如何更新袍暴?

  • 配套的,前端靜態(tài)資源應(yīng)該如何組織隶症?
  • 配套的政模,自動化構(gòu)建 & 部署過程如何與 CDN 結(jié)合?
  • 如何避免前端上線蚂会,影響未刷新頁面的用戶淋样?
  • 剛上線的版本發(fā)現(xiàn)有阻塞性 bug,如何做到秒級回滾颂龙,而非再次部署等 20 分鐘甚至更久习蓬?
  • 如何實現(xiàn)一個預(yù)發(fā)環(huán)境,除了前端資源外都是線上環(huán)境措嵌,將變量控制前端環(huán)境內(nèi)躲叼?
  • 部署環(huán)節(jié)如何方便配套做 AB 測試等?
  • 如何實現(xiàn)一套前端代碼企巢,發(fā)布成多套環(huán)境產(chǎn)物枫慷?
  • 如何實現(xiàn)按 feature 發(fā)布產(chǎn)物供用戶使用,并逐步擴(kuò)大 feature 灰度,將影響減到最谢蛱(即線上同時存在多 feature 產(chǎn)物)探孝?
  • CDN 域名突然掛了,如何實現(xiàn)秒級 CDN 降級修補(bǔ)而非再次全部業(yè)務(wù)重新部署一次誉裆?

本文將會帶著這些問題顿颅,試著一起探索在2021年,系統(tǒng)化的前端部署解決方案足丢。

PS:本篇關(guān)于靜態(tài)資源組織的問題&思路等粱腻,借鑒自知乎大佬張云龍這篇回答 大公司里怎樣開發(fā)和部署前端代碼

靜態(tài)資源組織

一個簡單的頁面

先從簡單的靜態(tài)頁面開始,眾所周知斩跌,前端資源由 HTML绍些、JavaScriptCSS 三劍客組成耀鸦,假設(shè)我們有一個簡單的頁面柬批,用 Nginx 作為 Web 服務(wù)器,資源組織結(jié)構(gòu)大概如下

[圖片上傳失敗...(image-cec736-1639121765208)]

此時袖订, 只需將 HTML氮帐、JavaScriptCSS 等靜態(tài)資源通過 FTP 等軟件著角,上傳到 Web 服務(wù)器(如 Nginx)某目錄揪漩,將 Nginx 啟動做簡單配置即可讓用戶訪問旋恼。

用戶一訪問吏口,狀態(tài) 200,頁面渲染出來冰更,前端十分簡單产徊,對不對?

利用緩存

但仔細(xì)觀察蜀细,用戶每次訪問都會請求 foo.css, bar.css 等靜態(tài)文件舟铜,即使該文件并無變更。對帶寬甚是浪費(fèi)奠衔,對頁面首屏性能等也有影響谆刨。于是在網(wǎng)絡(luò)帶寬緊張的互聯(lián)網(wǎng)早期,計算機(jī)先賢們在 HTTP 協(xié)議上制定了多種緩存策略归斤。

瀏覽器緩存:瀏覽器緩存(Brower Caching)是瀏覽器對之前請求過的文件進(jìn)行緩存痊夭,以便下一次訪問時重復(fù)使用,節(jié)省帶寬脏里,提高訪問速度她我,降低服務(wù)器壓力。

協(xié)商緩存

一種策略是瀏覽器先問問服務(wù)器有沒有變化,沒變化就用舊資源番舆。畢竟"問一問"的通信成本酝碳,遠(yuǎn)小于每次重新加載資源的成本。大致流程如下:

協(xié)商緩存: 向服務(wù)器發(fā)送請求恨狈,服務(wù)器會根據(jù)這個請求的 Request Header 的一些參數(shù)來判斷是否命中協(xié)商緩存疏哗,如果命中,則返回 304 狀態(tài)碼并帶上新的 Response Header 通知瀏覽器從緩存中讀取資源禾怠;

image.png

此時沃斤,使用協(xié)商緩存后,Network 大致變成了這樣:

image.png

注:協(xié)商緩存一般可在服務(wù)端通過設(shè)置 Last-Modifed刃宵、ETagResponseHeader 實現(xiàn)衡瓶。
注:304 狀態(tài)碼,表示資源未發(fā)生變更牲证,可使用瀏覽器緩存哮针。

強(qiáng)緩存

這樣,通過協(xié)商緩存坦袍,我們大幅優(yōu)化了資源未變更時的網(wǎng)絡(luò)請求十厢,節(jié)約大量帶寬,網(wǎng)站首屏性能也有不錯的提升捂齐,美滋滋蛮放!
然而仔細(xì)觀察,發(fā)現(xiàn)仍然有協(xié)商的過程奠宜,一百個靜態(tài)文件就有一百個協(xié)商請求包颁。在資源未發(fā)生變更時,追求極致的我們也應(yīng)該優(yōu)化掉這個協(xié)商請求压真,畢竟沒有買賣就沒有傷害娩嚼!
和協(xié)商緩存對應(yīng)的是使用強(qiáng)緩存,大概過程如下:

強(qiáng)緩存:瀏覽器不會向服務(wù)器發(fā)送任何請求滴肿,直接從本地緩存中讀取文件并返回Status Code: 200 OK岳悟。

此時,強(qiáng)緩存的大致對話過程如圖:

image.png

注意泼差,緩存生效期間贵少,瀏覽器是【自言自語】,和服務(wù)器無關(guān)堆缘。

此時滔灶,設(shè)置強(qiáng)緩存后,Network 大致變成了這樣:

image.png

From DiskCache:從硬盤中讀取套啤。
From MemoryCache:從內(nèi)存中讀取宽气,速度最快随常。
注:強(qiáng)緩存一般可在服務(wù)端通過設(shè)置 Cache-Control:max-ageExpiresResponseHeader 實現(xiàn)萄涯。

用上強(qiáng)緩存后绪氛,協(xié)商的請求也被消滅了,網(wǎng)站加載的性能達(dá)到極致了涝影。美滋滋枣察!

附錄:協(xié)商緩存和強(qiáng)緩存詳解

強(qiáng)緩存/協(xié)商緩存詳解

注:校招生或客戶端轉(zhuǎn)前端同學(xué),關(guān)于強(qiáng)緩存/協(xié)商緩存的實現(xiàn)及使用先了解即可燃逻。
后續(xù)再熟練掌握序目。

緩存更新問題

鑒于頁面(index.html)會頻繁更新,而靜態(tài)資源則相對穩(wěn)定伯襟。所以猿涨,我們能推斷出的一種緩存策略是 index.html 適合走協(xié)商緩存,相對穩(wěn)定 & 不常更新的靜態(tài)資源(JS姆怪、CSS叛赚、IMAGES) 等應(yīng)該消滅協(xié)商請求,使用強(qiáng)緩存稽揭。
然而問題很快就來了俺附,都不讓瀏覽器發(fā)請求,但緩存還未到期我們發(fā)現(xiàn)有 bug溪掀,想更新 foo.css 怎么辦事镣?
又想設(shè)置盡量長的時間走緩存,又想要能隨時更新揪胃?
又想馬兒跑又不給馬兒吃草璃哟?

image.png

相信大家很快就能得出一種思路,給資源加版本號只嚣!比如通過 query 加版本號沮稚,每次上線統(tǒng)一改版本號就搞定了。此時 HTML 變成如圖:

image.png

注意册舞,此時服務(wù)器內(nèi)只有一份文件 foo.css 文件。

統(tǒng)一加版本號的優(yōu)點(diǎn)是簡單粗暴快捷障般,但缺點(diǎn)則是:假如我們只想更新 foo.css调鲸,但 bar.css 緩存也失效了,又造成了帶寬的浪費(fèi)挽荡。
大家應(yīng)該很快就能想到辦法藐石,需要將文件內(nèi)容與版本號(URL)綁定,當(dāng)文件內(nèi)容發(fā)生變更時才變更版本號(URL)定拟,這樣就能實現(xiàn)每個文件精確的緩存控制于微。
什么東西與文件內(nèi)容相關(guān)呢逗嫡? 消息摘要算法 ,對文件求摘要信息株依,摘要信息與文件內(nèi)容一一對應(yīng)驱证,就有了一種可以精確到單個文件粒度的緩存控制依據(jù)。現(xiàn)在恋腕,我們把 URL 改成帶文件摘要信息的:

image.png

我們可以稱這種這個方式為 query-hash抹锄,后續(xù)發(fā)版上線時,只有被變更文件的 URL 會更新荠藤,實現(xiàn)了精確的緩存控制伙单,完美!

注意哈肖,此時服務(wù)器內(nèi)只有一份文件 foo.css 文件吻育。

覆蓋式發(fā)布引發(fā)的問題

然而假如我們就按上述部署方案就上了線,很快就會 Fatal 滿天飛淤井,每次更新上線都可能會出現(xiàn)災(zāi)難扫沼。
我們回顧一下,網(wǎng)站的靜態(tài)文件只有一份庄吼,部署在 Nginx 服務(wù)器某目錄下缎除,并且通過 query-hash 的方式實現(xiàn)按文件做精確緩存控制,問題出在哪了呢总寻?
回顧一下器罐,我們某次更新時,更改了 foo.css 樣式渐行,此時會將 HTML 中的foo.css url更新為最新的 hash轰坊,并將服務(wù)器中存儲的 foo.css & index.html 文件覆蓋為最新(V2版本),看似HTML和靜態(tài)資源都對應(yīng)更新了祟印,但是沒有考慮極端情況肴沫。那就是:

  1. 先部署靜態(tài)資源,部署期間訪問時蕴忆,會出現(xiàn)V1版本HTML訪問到V2版本新靜態(tài)資源颤芬,并按V1-hash緩存起來
  2. 先部署HTML套鹅,部署期間訪問時站蝠,會出現(xiàn)V2版本HTML訪問到V1版本舊靜態(tài)資源,并按V2-hash緩存起來卓鹿。

如下圖所示菱魔,展示了不同版本HTML與不同版本靜態(tài)資源互相匹配到出現(xiàn)的異常Case。

image.png

綠色走向:正常訪問并建立緩存的路徑吟孙。
紅色走向:先部署靜態(tài)資源(V2)澜倦,V1-HTML訪問V2靜態(tài)資源并緩存Case
黑色走向:先部署HTML(V2)聚蝶,V2-HTML訪問V1資源并緩存Case

對于問題1,會有兩種子Case:

  1. 用戶本地有緩存藻治,此時無影響可正常訪問碘勉。
  2. 用戶本地?zé)o緩存,則會將V2版本靜態(tài)資源加載并按V1版本 hash 緩存起來栋艳。用戶報錯恰聘。當(dāng)V2版本HTML部署完成后,用戶再次訪問時恢復(fù)吸占。

對于問題2晴叨,則會出現(xiàn)嚴(yán)重的Case:
V2 版本HTML,會將V1版本靜態(tài)資源按V2版本Hash緩存起來矾屯。此時頁面會出錯兼蕊,且緩存過期之前會持續(xù)報錯。直到用戶手動清除緩存件蚕,或者緩存過期孙技,或者將來發(fā)布V3版本更新靜態(tài)資源版本。否則用戶會持續(xù)出錯排作。
上面方案的問題起源于靜態(tài)資源只有一份牵啦,每次發(fā)布時都是覆蓋式發(fā)布,導(dǎo)致頁面與靜態(tài)資源出現(xiàn)匹配錯誤的情況妄痪!解決問題方案也極其簡單哈雏,使用非覆蓋式發(fā)布,一種簡單的改造方式是將文件摘要(hash)放置到URL 中衫生,即將 query-hash 改為 name-hash裳瘪。
此時 HTML 變成如圖:

image.png

這樣,每次部署時先全量部署靜態(tài)資源罪针,再灰度部署頁面彭羹,就能比較完美的解決了緩存的問題。

此時泪酱,服務(wù)器上會存在多份 foo.[$hash].css 文件

與 CDN 結(jié)合

現(xiàn)在我們開開心心將網(wǎng)站部署上線了派殷,但我們此時仍然將靜態(tài)資源部署在 Nginx 服務(wù)器目錄下,然后新的問題來了西篓,隨著時間推移愈腾,非覆蓋部署導(dǎo)致文件逐漸增加多,硬盤逐漸吃緊岂津。而且將文件存儲在 Nginx Web服務(wù)器內(nèi)某目錄下,深度的將Nginx 悦即、網(wǎng)站吮成、部署過程等強(qiáng)耦合在一起橱乱,無法使用 CDN 技術(shù)

CDN 是一種內(nèi)容分發(fā)網(wǎng)絡(luò),部署在應(yīng)用層粱甫,利用智能分配技術(shù)泳叠,根據(jù)用戶訪問的地點(diǎn),按照就近訪問的原則分配到多個節(jié)點(diǎn)茶宵,來實現(xiàn)多點(diǎn)負(fù)載均衡危纫。
簡單來說,用戶就近訪問乌庶,訪問速度更快种蝶,大公司也無需搞一臺超級帶寬的存儲服務(wù)器,只需使用多臺正常帶寬的 CDN 節(jié)點(diǎn)即可瞒大。
而 CDN 的常見實現(xiàn)是有一臺源站服務(wù)器螃征,多個 CDN 節(jié)點(diǎn)定時從源站同步。

那如何將 CDNNginxWeb 服務(wù)器結(jié)合呢透敌?
答案是將靜態(tài)資源部署到 CDN 上盯滚,再將 Nginx 上的流量轉(zhuǎn)發(fā)到 CDN 上,這種技術(shù)我們稱之為『反向代理』酗电。
此時魄藕,用戶訪問時流量走向 & 研發(fā)構(gòu)建部署過程大致如下:

image.png

此時,我們總體部署方案需要進(jìn)一步做三步改造撵术。

  1. 構(gòu)建時依據(jù)環(huán)境變量背率,將 HTML 中的靜態(tài)資源地址加上 CDN 域名。
  2. 構(gòu)建完成后將靜態(tài)資源上傳到 CDN 荷荤。
  3. 配置 Nginx 的反向代理退渗,將靜態(tài)資源流量轉(zhuǎn)發(fā)到 CDN

其中蕴纳,第 1会油、2 條涉及構(gòu)建過程調(diào)整磕秤,以 Webpack 為例瞎抛,我們需要做以下配置改造:

a.  配置 `output` 為 `content-hash` & `publicPath`  
b.  配置 `Webpack-HTML-Plugin`

下面是一個配置示例:

image.png
// webpack.config.js
const CDN_HOST = process.env.CDN_HOST;// CDN 域名
const CDN_PATH = process.env.CDN_PATH; // CDN 路徑
const ENV = process.env.ENV; // 當(dāng)前的環(huán)境等等
const VERSION = process.env.VERSION; // 當(dāng)前發(fā)布的版本

const getPublicPath = () => {
    // Some code here
    return `${CDN_HOST}/${CDN_PATH}/${ENV}/`;// 依據(jù) ENV 等動態(tài)構(gòu)造 publicPath
}

const publicPath = process.env.NODE_ENV === 'production' ? getPublicPath() : '.';

module.exports = {
    output: {
        filename: 'bundle.[name][contenthash:8].js',
        publicPath,
    },
    plugins: [
        new HtmlWebpackPlugin()
    ]
}

備注1:我們往往會將一套代碼部署到多套前端環(huán)境,還需要在構(gòu)建時注入當(dāng)前部署相關(guān)環(huán)境變量(如 staging褂萧、prod稻薇、dev嫂冻、pre等),以便動態(tài)構(gòu)建 publicPath塞椎。
備注 2:這里動態(tài)構(gòu)造的 publicPath 里桨仿,嚴(yán)格的將產(chǎn)物按環(huán)境 + 發(fā)布版本做了隔離 & 收斂。 某業(yè)務(wù)前端曾將所有環(huán)境的靜態(tài)資源放到一起案狠,以Hash做區(qū)分服傍。但疑似出現(xiàn)了文件名 + hash 沖突钱雷,但文件內(nèi)容不一樣,導(dǎo)致了線上事故吹零。故墻裂建議嚴(yán)格對產(chǎn)物做物理隔離罩抗。
備注 3:publicPath 詳解webpack.docschina.org/configurati…
備注 4:此處使用了 content-hash,與 hash灿椅、chunkhash 的區(qū)別請見:詳解webpack中的hash套蒂、chunkhash、contenthash區(qū)別
備注 5:使用 contenthash 時茫蛹,往往會增加一個小模塊后操刀,整體文件的 hash 都發(fā)生變化,原因為Webpackmodule.id 默認(rèn)基于解析順序自增麻惶,從而引發(fā)緩存失效馍刮。具體可通過設(shè)置 optimization.moduleIds 設(shè)置為 'deterministic'
具體詳見 webpack 官方文檔-緩存
注:關(guān)于 Webpack 的配置窃蹋,校招生或客戶端轉(zhuǎn)前端同學(xué)卡啰,前期了解即可,后續(xù)建議深入學(xué)習(xí)警没。

  1. 構(gòu)建完成后靜態(tài)資源上傳 CDN 源站

上傳 CDN 源站往往通過 CLI 調(diào)用各種客戶端工具上傳匈辱,此時要注意的是上傳 CDN 依賴配置鑒權(quán)信息(如 文件存儲的 Bucket Name/accessKey、ftp的賬號密碼)杀迹。這些鑒權(quán)信息不能直接寫代碼里亡脸,否則可能會有事故風(fēng)險(想想為什么)!
第 3 步改造是 Nginx 層反向代理改造

反向代理(reverse proxy):是指以代理服務(wù)器來接受網(wǎng)絡(luò)請求树酪,并將請求轉(zhuǎn)發(fā)給內(nèi)部的服務(wù)器浅碾,并且將內(nèi)部服務(wù)器的返回,就像是二房東一樣续语。
一句話解釋反向代理 & 正向代理:反向代理隱藏了真正的服務(wù)器垂谢,正向代理隱藏了真正的客戶端。
詳見:漫話:如何給女朋友解釋什么是反向代理疮茄?

Nginx 可通過設(shè)置 proxy_pass 配置代理轉(zhuǎn)發(fā)滥朱,如

location ^~/static/ {
    proxy_pass $cdn;
}

具體詳見 nginx 之 proxy_pass詳解

注:校招生或客戶端轉(zhuǎn)前端同學(xué),前期了解即可力试,后續(xù)建議熟悉 ~ 掌握徙邻。

靜態(tài)資源組織總結(jié)

最后,回顧一下

  1. 為了最大程度利用緩存畸裳,將頁面入口(HTML)設(shè)置為協(xié)商緩存缰犁,將 JavaScript、CSS 等靜態(tài)資源設(shè)置為永久強(qiáng)緩存。
  2. 為了解決強(qiáng)緩存更新問題民鼓,將文件摘要(hash)作為資源路徑(URL)構(gòu)成的一部分薇芝。
  3. 為了解決覆蓋式發(fā)布引發(fā)的問題蓬抄,采用 name-hash 而非 query-hash 的組織方式丰嘉,具體需要配置 Wbpack 的 output.filename 為 contenthash 。
  4. 為了解決 Nginx 目錄存儲過大 + 結(jié)合 CDN 提升訪問速度嚷缭,采用了 Nginx 反向代理+ 將靜態(tài)資源上傳到 CDN饮亏。
  5. 為了上傳 CDN,我們需要按環(huán)境動態(tài)構(gòu)造 publicPath + 按環(huán)境構(gòu)造 CDN 上傳目錄并上傳阅爽。
  6. 為了動態(tài)構(gòu)造 publicPath 并且隨構(gòu)建過程插入到 HTML 中路幸,采用 Webpack-HTML-Plugin 等插件,將編譯好的帶 hash + publicPath 的靜態(tài)資源插入到 HTML 中付翁。
  7. 為了保證上傳 CDN 的安全简肴,我們需要一種機(jī)制管控上傳 CDN 秘鑰,而非簡單的將秘鑰寫到代碼 / Dockerfile 等明文文件中百侧。

此時砰识,我們已經(jīng)基本獲得了一套相對完備的前端靜態(tài)資源組織方案。

此時你可能已經(jīng)發(fā)現(xiàn)了佣渴,前端靜態(tài)資源部署后辫狼,還有被 Nginx 加工消費(fèi)過程,才能被用戶訪問到辛润。

自動化構(gòu)建

現(xiàn)在我們已經(jīng)探索出一套靜態(tài)資源組織的解決方案∨虼Γ現(xiàn)在探討一下構(gòu)建的過程。我們每次構(gòu)建時大約需要進(jìn)行這些步驟:

  • 拉取遠(yuǎn)程倉庫
  • 切換到 XX 分支
  • 代碼安全檢查(非必選)砂竖、單元測試等等
  • 安裝 npm/yarn 依賴
    • 設(shè)置 node 版本
    • 設(shè)置 npm/yarn 源
    • 安裝依賴等
  • 執(zhí)行編譯 & 構(gòu)建
  • 產(chǎn)物檢查(比如檢測打包后 JS 文件 / 圖片大小真椿、產(chǎn)物是否安全等,保證產(chǎn)物質(zhì)量乎澄,非必選)
  • 人工卡點(diǎn)(非必選突硝,如必須 Leader 審批通過才能繼續(xù))
  • 打包上傳 CDN
  • 自動化測試(非必選,e2e)
  • 配套剩余其他步驟
  • 通知構(gòu)建完成

這其中三圆,迎面而來的問題有:

  • 在什么環(huán)境執(zhí)行構(gòu)建狞换?
  • 如何保證每次構(gòu)建部署環(huán)境相同?
  • 由誰觸發(fā)構(gòu)建舟肉?
  • 如何管理前面所述上傳 CDN 等密鑰(不增加成本修噪、保證安全、保證構(gòu)建上傳可靠性)路媚?
  • 如何自動化觸發(fā)構(gòu)建 & 自動化執(zhí)行上述步驟黄琼?
  • 如何提升構(gòu)建速率?
  • 構(gòu)建完成如何通知研發(fā)同學(xué)構(gòu)建完成了?

為了解決上面問題脏款,業(yè)界有一些解決方案:

  • 保證環(huán)境一致性:Docker
  • 按流程構(gòu)建:Jenkins
  • 自動化構(gòu)建觸發(fā):Gitlab webhook 通知
  • 開始構(gòu)建通知:依賴賬號體系打通+ Gitlab Webhook
  • 構(gòu)建完成通知:依賴賬號體系打通

業(yè)界的大致實現(xiàn)围苫,一般都為 Jenkins + Docker + GitlabWebHook,比如下面是一些實踐:

前端項目自動化部署——超詳細(xì)教程(Jenkins撤师、Github Actions)
iDeploy-為前端團(tuán)隊構(gòu)建部署工程化而開發(fā)的一個持續(xù)交付平臺

此時還有一些其他問題:
比如宇宙最重物質(zhì) node_modules 安裝速度過慢的問題剂府?
如何提升 Build 構(gòu)建速?
上述往往在各大公司都有相對完善的構(gòu)建系統(tǒng) & 解決方案等,各公司各不相同但大致類似剃盾,故本文跳過該步驟腺占。

前端發(fā)布服務(wù) - 預(yù)發(fā)環(huán)境、版本管理(秒級回滾)痒谴、小流量衰伯、灰度、AB測試

假定我們靜態(tài)資源組織完成积蔚,也搞定了自動化構(gòu)建部署意鲸,也配好了 Nginx 的反向代理,我們的網(wǎng)站終于第一次上線了尽爆。
但第二次第三次上線怎么辦怎顾?直接發(fā)到生產(chǎn)環(huán)境做回歸測試的風(fēng)險極大,但又不能本地部署前端測試環(huán)境去連接后端生產(chǎn)庫(可以想想為什么)教翩,所以我們需要一個預(yù)發(fā)(Pre)環(huán)境杆勇,除了非測試人員訪問不到之外,其他所有環(huán)節(jié)都和生產(chǎn)環(huán)境保持一致饱亿!
此時遇到第一個需求蚜退,預(yù)發(fā)環(huán)境功能
假如我們某個功能是元旦零點(diǎn)發(fā)布彪笼,跨年時守在服務(wù)器面前點(diǎn)發(fā)布钻注?萬一 npm 抽風(fēng)拉取依賴失敗導(dǎo)致構(gòu)建失敗,或者上線后發(fā)現(xiàn)有 bug 配猫,那就只能涼涼幅恋。
或者,隨著時間推移大家前端項目越積越大泵肄,node_modules 質(zhì)量逐漸超越銀河系總質(zhì)量捆交,構(gòu)建的時間往往會超過二十分鐘甚至更久。某天某次我們新上線了功能后腐巢,卻發(fā)現(xiàn)有致命阻塞性 bug 品追,收款后自動退款 1.5 倍!想立即回滾版本冯丙?那就且等著肉瓦,大眼瞪小眼的等它慢慢編譯吧。這個時候才真的是時間就是金錢,再編譯慢點(diǎn)公司就破產(chǎn)啦泞莉。
此時有沒有一種辦法哪雕,能在發(fā)現(xiàn)問題后,立即將版本回滾呢鲫趁?并且這個回滾操作斯嚎,回滾的同學(xué)也不應(yīng)該登陸服務(wù)器去做操作(想想為什么?)饮寞。
此時遇到第二個需求孝扛,版本管理功能。 即可提前將靜態(tài)資源上線幽崩,也需要保留每個歷史版本,并且能實現(xiàn)瞬間切換版本寞钥,且切換過程不應(yīng)該登陸服務(wù)器操作(想想為什么)慌申。
其次是,假定 PM 對功能不斷優(yōu)化理郑,想先灰度一部分用戶蹄溉,或者想做一些 AB 測試,比如給廣東用戶推廣福建美食您炉,給重慶用戶推廣缽缽雞柒爵。
此時我們有兩種方案,方案一是將把缽缽雞和福建美食都打包到一份代碼產(chǎn)物里赚爵,再在運(yùn)行時根據(jù)地域做切換棉胀。但很快你的代碼產(chǎn)物里就有缽缽雞冷鍋串串熱鍋串串老媽兔頭蹺腳牛肉狼牙土豆以及福建美食等等,會串味兒的對不對冀膝?況且熱鍋串串和冷鍋串串打包混到一起我就第一個不同意唁奢,簡直是對美食的褻瀆!所以方案一不可取窝剖。

實際上麻掸,現(xiàn)實中往往會熱鍋串串冷鍋串串這樣完全不兼容的兩份改動同時在線上運(yùn)行做 AB 測試。

方案二是我們將熱鍋串串和冷鍋串串分開打包赐纱,讓熱鍋不犯冷鍋脊奋。再設(shè)計一些機(jī)制,比如攜帶了香蕉糖果(cookie)的同學(xué)給蹺腳牛肉鍋疙描,講港東話的同學(xué)福建美食鍋诚隙,四川地域的同學(xué)隨機(jī)給火鍋干鍋湯鍋魚火鍋。豈不樂哉淫痰?
大家應(yīng)該很容易發(fā)現(xiàn)最楷,這種機(jī)制是極其多變的,大概率朝令夕改。難道我們每次想調(diào)整干鍋籽孙、魚火鍋的比例烈评,就要登陸服務(wù)器做調(diào)整?某天干鍋賣完了但又沒帶電腦回家怎么辦犯建?
此時遇到第三個需求讲冠,隨時調(diào)整的小流量測試AB-Test測試适瓦、灰度上線等等功能竿开。
總結(jié)一下,為了滿足復(fù)雜的線上需求玻熙,在部署層面總體來說需要:預(yù)發(fā)環(huán)境否彩、版本管理、小流量嗦随、灰度列荔、AB測試等功能。

靜態(tài)資源的加工

如前所述枚尼,前端靜態(tài)資源部署到 CDN 后贴浙,有一道 Nginx 反向代理做轉(zhuǎn)發(fā)的加工工序。事實上署恍,為了解決各種部署問題或為了提升性能崎溃,人們往往而需要對靜態(tài)資源做更多的加工工序。
比如盯质,部分 Web 應(yīng)用為了提升首屏性能袁串,一種常見的方式為通過 BFF 層或通過后端直出 HTML,并且在過程中注入若干信息唤殴,如 userInfo般婆、用戶權(quán)限信息、灰度信息等等朵逝,從而大幅降低前端登陸研發(fā)成本 & 降低首屏耗時蔚袍。
下面是后端直出 HTML 的一種簡要流程。

image.png

主要流程為前端構(gòu)建出的 HTML 包含若干模板變量配名,后端收到請求后啤咽,通過各種 Proxy 層將 Cookie 轉(zhuǎn)換成用戶信息,再按依據(jù)版本配置從 CDN 加載 index.html, 并使用模板引擎等方式將模板變量替換為用戶信息渠脉,最終吐回給瀏覽器的則是已經(jīng)包含用戶信息的 HTML 了宇整!

Pre 環(huán)境、灰度上線的常見實現(xiàn)

如前所述芋膘,我們的靜態(tài)資源為非覆蓋式發(fā)布鳞青,多次部署后霸饲,線上存在若干版本靜態(tài)資源。實現(xiàn)Pre環(huán)境/灰度上線的思路則是:通過一定的機(jī)制臂拓,讓特定用戶訪問特定靜態(tài)資源版本厚脉,從而達(dá)到訪問Pre/灰度上線的能力。

方案一 Nginx 層動態(tài)轉(zhuǎn)發(fā)

一種常見的 Pre 機(jī)制是靜態(tài)資源部署多個版本后胶惰,開發(fā)者的通過 ModHeader 等瀏覽器插件傻工,在請求中攜帶特定 Header(如xx-env=pre),在 Nginx 層消費(fèi)該 Header 并動態(tài)轉(zhuǎn)發(fā)到對應(yīng)環(huán)境的靜態(tài)資源上孵滞,實現(xiàn)訪問 Pre 環(huán)境目的中捆。此時,除靜態(tài)資源為特定版本外坊饶,所有環(huán)境都是生產(chǎn)環(huán)境泄伪,可以將變量范圍控制在最小。

流程大致如圖:


image.png

Nginx 可通過配置 rewrite 設(shè)置轉(zhuǎn)發(fā)幼东,如下所示臂容。

詳情請查閱:nginx配置rewrite指令詳解

location /example {
    rewrite ^ $cdn/$http_x_xx_env/index.html break;
    proxy_pass $cdn/prod/index.html;
}

# $http_x_xx_env 表示取自定義的 Request Header 字段 xx_env

注:對于Nginx,校招生或客戶端轉(zhuǎn)前端同學(xué)根蟹,前期了解即可,后續(xù)建議熟悉 ~ 掌握糟秘。

該方案優(yōu)點(diǎn)為配置簡單高效简逮,適用于工程師。
缺點(diǎn)為每個用戶都需要手動配置尿赚,不適用于移動端散庶,且無法讓特定用戶被動精確訪問某版本,比如 PM凌净、KP 用戶來配置 Header 成本過高悲龟。
同理,也可以在 Nginx 層按一些其他規(guī)則處理冰寻,實現(xiàn)灰度上線的能力须教。
如通過一定隨機(jī)數(shù) rewrite,達(dá)到小范圍隨機(jī)灰度斩芭。
獲取 ua 并 rewrite轻腺,達(dá)到按瀏覽器定向灰度。
通過 Nginx GeoIP 獲取地域信息划乖,達(dá)到按地域灰度贬养。
但上述灰度方案配置復(fù)雜,而灰度比例 / 范圍往往會配置較多琴庵,每次上線都需要運(yùn)維登陸生產(chǎn)服務(wù)器修改误算,較容易出各種事故仰美。故不推薦使用,僅供拓寬思路儿礼。

方案二 動態(tài)配置 + 服務(wù)端轉(zhuǎn)發(fā)

但 Pre 環(huán)境或灰度往往需要精確定位某些特定人群咖杂,如給特定PM、HR蜘犁、遠(yuǎn)端報錯的特定用戶翰苫、KP用戶 甚至給某個部門開 Pre環(huán)境等。上述同學(xué)工程背景相對缺失 / 較忙 / 通過移動端訪問这橙,此時通過修改 Header 的方式不再適用奏窑。故我們?nèi)匀灰獙ふ夷撤N機(jī)制,達(dá)到能方便隨時調(diào)整 Pre/ 灰度范圍又不用重新發(fā)版上線屈扎。既然需要按用戶維度來定向埃唯,此時就依賴后端幫忙處理了。
而為了能隨時隨地調(diào)整灰度 / Pre 策略鹰晨,而非依賴調(diào)整代碼發(fā)版上線墨叛,此時引入配置中心的概念。

配置中心:一般是獨(dú)立的平臺 / SDK模蜡,提供動態(tài)配置管理的解決方案漠趁,提供功能有配置管理、版本管理忍疾、權(quán)限管理闯传、灰度發(fā)布等等。后端應(yīng)用通過接口消費(fèi)卤妒,故配置中心和后端解耦甥绿,可以隨時修改調(diào)整配置而非重新發(fā)版。
配置中心一般是配置一個 JSON 對象则披。
配置中心JSON對象人工維護(hù)容易引發(fā)問題共缕,故增加機(jī)器人來降低出錯幾率。

下圖是依賴配置中心 + 服務(wù)端轉(zhuǎn)發(fā)的流程圖:

image.png

主要流程為:

  1. 前端攻城獅同學(xué)部署多個版本靜態(tài)資源到 CDN 上(問題士复?如何管控多版本靜態(tài)資源图谷?)。
  2. 后端收到請求后判没,通過各種 Proxy 層將 Cookie 轉(zhuǎn)換成用戶信息蜓萄。
  3. 后端讀取配置中心數(shù)據(jù),依據(jù)用戶信息判斷給用戶訪問什么環(huán)境澄峰,加載具體環(huán)境 index.html
  4. 后端返回給瀏覽器加工后的 index.html
  5. 若需添加具體 KP 等同學(xué)到 Pre 名單嫉沽,攻城獅同學(xué)只需調(diào)用機(jī)器人/Bot 等,修改配置中心俏竞,即可生效绸硕。

注意堂竟,在上述架構(gòu)下,若線上某用戶發(fā)生某些難以排查的問題玻佩,也可發(fā)布特定的版本出嘹,在配置中心修改后讓用戶訪問特定版本頁面,從而簡化排查問題的過程咬崔。

此時税稼,一些小流量配置,AB實驗垮斯,版本管理其實也可以通過該方案實施郎仆。
該方案優(yōu)點(diǎn):可以隨時調(diào)整,不用后端發(fā)版兜蠕,移動端也可生效扰肌。
該方案缺點(diǎn):

  1. 和服務(wù)端強(qiáng)綁定(要求用戶信息,在所難免)熊杨。
  2. 每次都需要從 CDN加載 HTML曙旭, 有一定性能浪費(fèi)。但若緩存 HTML晶府,發(fā)版環(huán)節(jié)還要通知服務(wù)端桂躏,總體增加復(fù)雜度。
  3. 若考慮 CDN 故障川陆,服務(wù)端做 CDN 降級會增加復(fù)雜度沼头。
  4. 版本管理 / 小流量等為通用需求,而該方案每個后端應(yīng)用都需要開發(fā)或接入书劝。
  5. 常見的配置中心又一般為 JSON 配置,比較簡陋土至,和發(fā)版的多環(huán)境無法關(guān)聯(lián)购对,依賴人為配置,有出錯的風(fēng)險(如發(fā)版 v1.2501陶因,配置中心手動配置時手誤改成了v1.2051)骡苞。

前端發(fā)布服務(wù)實現(xiàn)與設(shè)計

可能部分同學(xué)對線上產(chǎn)物實行版本管理會誤理解對代碼增加版本管理(如發(fā)版后手動 / 自動打Tag),后續(xù)需要時再次發(fā)版部署即可滿足需求楷扬。但如前所述解幽,通過源碼做版本管理靈活性較差,無法做到一鍵 &秒級切換版本烘苹,不滿足商業(yè)化環(huán)境多變 & 復(fù)雜的需要躲株。
那么如何進(jìn)行版本管理呢?答案是對構(gòu)建產(chǎn)物進(jìn)行深層次加工 & 管理镣衡。
與此同時霜定,版本管理/小流量是前端部署的常見公共業(yè)務(wù)需求档悠,應(yīng)該和業(yè)務(wù)后端服務(wù)脫離,故這里提出一個新的公共服務(wù)望浩,純用于前端部署相關(guān)辖所,此處將之稱為 Page Server,用于具體的 index.html 文件管理 & 承接 Nginx 流量或業(yè)務(wù)后端流量等磨德。
同時缘回,鑒于版本管理、小流量策略等調(diào)整會特別頻繁典挑,每次調(diào)整不應(yīng)該都登錄服務(wù)器酥宴,故我們需要一個新的服務(wù) & 界面,用于操作管理版本搔弄、調(diào)整小流量等信息幅虑,并且與上述 Page Server 同步,此處我們將該服務(wù)稱之為 Page Config Web顾犹。
而我們的 Page Server 則可能會有很多個實例倒庵,部署在多個集群上,以滿足跨國部署炫刷、多部門項目部署等要求擎宝。所以理想情況下 Page Config Web 還要承接 PageServer 的創(chuàng)建、管理浑玛、配置等工作绍申。所以 PageConfigWebPageServer 是 1 比 N 關(guān)系(或M比N,用于跨國部署等)顾彰。
同時极阅,我們一個前端項目可能有多套前端環(huán)境,PageSever 在固定集群算公共設(shè)施涨享,這些環(huán)境理論上都可以由一個或多個 PageServer 承載筋搏。故一個 PageServer 和多個前端環(huán)境是 1 比 1 或者 1 比 N 關(guān)系。
此時厕隧,對于 Nginx 來的流量奔脐,我們需要一種機(jī)制來區(qū)分該流量屬于哪個環(huán)境實例,比如通過 URL 來區(qū)分吁讨,我們可以稱之為 路由髓迎。
最后,為了保證上述服務(wù)的正確性和自動化建丧,構(gòu)建部署(新增版本)完成后排龄,要同步到上述兩個服務(wù),以確保版本管理的正確性茶鹃。
最后涣雕,大致的流程圖如下:

image.png

本質(zhì)上來說艰亮,相當(dāng)于有一個公用的中間服務(wù),部署在多個集群上挣郭,與構(gòu)建發(fā)布過程深度綁定迄埃,用于承接HTML 的流量,并通過 Web 站點(diǎn)設(shè)置小流量規(guī)則兑障、版本等等侄非,來滿足多變的上線需求。
其中流译,PageServer 在承載 HTML 服務(wù)時逞怨,可做一些其他工作,比如:

  1. SSR
  2. CDN 降級福澡,用于 CDN 異常時直出 HTML 中將靜態(tài)資源替換為可用的 CDN 站點(diǎn)叠赦。
  3. 404 處理
  4. 兜底頁(比如服務(wù)出現(xiàn)故障,短時間內(nèi)無法修復(fù)時出兜底)
  5. 模板渲染(如做模板替換革砸,將 query 替換到模板中等)
  6. 特殊時期全局處理除秀,如注入全局樣式將頁面全局置灰

PageConfig Web 和 PageServer 中有構(gòu)建后的所有版本信息,理論上可以緩存每個版本的 HTML文件算利,并且為了優(yōu)化性能册踩,PageServer 中可將最新全量版本的 HTML 文件緩存到內(nèi)存中,最大程度提升響應(yīng)速度效拭,其余版本存儲到 Redis 等緩存中暂吉。

下面以發(fā)布一個正式版本 v.1.0.2502 并且回滾過程為例:

  1. 代碼合并,觸發(fā)自動化構(gòu)建缎患,構(gòu)建產(chǎn)物以環(huán)境(env)+版本(env) + 版本(env)+版本(version) + name-hash 方式組織慕的,并上傳到 CDN。
  2. 構(gòu)建完成后挤渔,構(gòu)建腳本通知攻城獅同學(xué)业稼、同步 PageServer、PageConfig Web 服務(wù)有新版本 v.1.0.2502 蚂蕴。
    3.攻城獅同學(xué)收到通知后,到 PageConfig Web 站點(diǎn)發(fā)布新版本 v.1.0.2502 (PRE)俯邓,并為該版本配置 PRE 環(huán)境小流量規(guī)則骡楼,xx-env = pre。此時稽鞭,只有設(shè)置特定 Header 才能訪問該版本鸟整。
  3. 若是 Nginx 直接轉(zhuǎn)發(fā),則攻城獅通過設(shè)置 Header 訪問 PRE 版本朦蕴。
  4. 若是通過服務(wù)端轉(zhuǎn)發(fā)篮条,攻城獅通過配置中心設(shè)置 PRE 白名單弟头,即可讓用戶訪問 PRE 版本。
  5. 在 PRE 版本驗收完成后涉茧,攻城獅登錄 PageConfig Web 站點(diǎn)赴恨,發(fā)布正式版本 v.1.0.2502 (不帶小流量信息)。此時立即生效伴栓。
  6. 生效后線上回歸伦连,發(fā)現(xiàn)有 bug,攻城獅立馬登錄 PageConfig Web 站點(diǎn)钳垮,將版本回滾為上一版本v.1.0.2501 惑淳。此時立即生效。

關(guān)于部署的總結(jié)

靜態(tài)資源組織部分

  1. 為了最大程度利用緩存饺窿,將頁面(HTML)設(shè)置為協(xié)商緩存歧焦,將 JavaScript、CSS 等設(shè)置為永久強(qiáng)緩存肚医。
  2. 為了解決強(qiáng)緩存更新問題绢馍,將文件摘要(hash)作為資源路徑(URL)構(gòu)成的一部分。
  3. 為了解決覆蓋式發(fā)布引發(fā)的問題忍宋,采用 name-hash 而非 query-hash 的組織方式痕貌,具體需要配置 webpack 的 output.filename 為 contenthash 方式。
  4. 為了解決 Nginx 目錄存儲過大 + 結(jié)合 CDN 提升訪問速度糠排,采用了 Nginx 反向代理+ 將靜態(tài)資源上傳到 CDN舵稠。
  5. 為了上傳 CDN,我們需要按環(huán)境動態(tài)構(gòu)造 publicPath + 按環(huán)境構(gòu)造 CDN 上傳目錄并上傳入宦。
  6. 為了動態(tài)構(gòu)造 publicPath 并且隨構(gòu)建過程插入到 HTML 中哺徊,采用 Webpack-HTML-Plugin 等插件,將編譯好的帶 hash + publicPath 的靜態(tài)資源插入到 HTML 中乾闰。
  7. 為了保證上傳 CDN 的安全落追,我們需要一種機(jī)制管控上傳 CDN 秘鑰,而非簡單的將秘鑰寫到代碼 / Dockerfile 等明文文件中涯肩。

自動化部署部分

為了提升部署效率轿钠,100% 避免因部署出錯,需要設(shè)計 & 搭建自動化部署平臺病苗,以 Docker 等保證環(huán)境的一致性疗垛,以 Jenkins 等保證構(gòu)建流程的串聯(lián)。使用es-build等提升構(gòu)建效率硫朦。

前端部署 & 靜態(tài)資源加工

關(guān)于前端部署贷腕,能總結(jié)出下面幾個原則/要求:

  1. 構(gòu)建發(fā)布后,不應(yīng)該被覆蓋。
  2. 構(gòu)建發(fā)布后泽裳,靜態(tài)資源應(yīng)當(dāng)永久保存在服務(wù)器/CDN 上瞒斩,即只可讀。
  3. 靜態(tài)資源組織上涮总,每個版本應(yīng)該按文件夾存儲胸囱,做到資源收斂。這樣假如真要刪除時妹卿,可按版本刪除旺矾。(如某個版本代碼泄密)
// webpack.config.js
const CDN_HOST = process.env.CDN_HOST;// CDN 域名
const CDN_PATH = process.env.CDN_PATH''; // CDN 路徑
const ENV = process.env.ENV; // 當(dāng)前的環(huán)境等等
const VERSION = process.env.VERSION; // 當(dāng)前發(fā)布的版本

const getPublicPath = () => {
    // Some code here
    return `${CDN_HOST}/${CDN_PATH}/${ENV}/${VERSION}/`;// 依據(jù) ENV 等動態(tài)構(gòu)造 publicPath
}

module.exports = {
    output: {
        filename: 'bundle.[name][contenthash].js',
        publicPath: getPublicPath(),
    },
    plugins: [
        new HtmlWebpackPlugin()
    ]
}

故 publicPath 應(yīng)增加 version 字段

  1. 發(fā)布過程應(yīng)該自動化,開發(fā)人員不應(yīng)該直接接觸服務(wù)器夺克。
  2. 版本切換時箕宙,也應(yīng)當(dāng)不接觸服務(wù)器。
  3. 版本切換能秒級生效。(如 v0.2 切換 v0.3,立即生效)律杠。
  4. 線上需要能同時生效多個版本醋火,滿足 AB 測試枷颊、灰度、PRE 環(huán)境等小流量需求。

上述需求都相對復(fù)雜多變,為了應(yīng)對復(fù)雜的線上需求凤跑,可以對靜態(tài)資源做深度加工,如通過服務(wù)端直出 HTML叛复、通過配置中心實現(xiàn)按用戶 PRE 等等仔引。

前端發(fā)布服務(wù)

面對復(fù)雜的商業(yè)化需求,方便多前端業(yè)務(wù)實現(xiàn)版本管理褐奥、灰度咖耘、PREAB 測試等小流量功能撬码,我們設(shè)計了一個中間服務(wù) PageConfig Web & PageServer儿倒,與 Nginx 和各種后端相結(jié)合,達(dá)到配置即時生效的能力呜笑。

靈魂拷問的部分答案

Q: 前端代碼從 tsx/jsx 到部署上線被用戶訪問夫否,中間大致會經(jīng)歷哪些過程?
A: 經(jīng)歷本地開發(fā)叫胁、遠(yuǎn)程構(gòu)建打包部署慷吊、安全檢查、上傳CDN曹抬、Nginx做流量轉(zhuǎn)發(fā)、對靜態(tài)資源做若干加工處理等過程。
Q:可能大部分同學(xué)都知道強(qiáng)緩存/協(xié)商緩存谤民,那前端各種產(chǎn)物(HTML堰酿、JS、CSS张足、IMAGES 等)應(yīng)該用什么緩存策略触创?以及為什么?

  • 若使用協(xié)商緩存为牍,但靜態(tài)資源卻不頻繁更新哼绑,如何避免協(xié)商過程的請求浪費(fèi)?
  • 若使用強(qiáng)緩存碉咆,那靜態(tài)資源如何更新抖韩?

A:HTML使用協(xié)商緩存,靜態(tài)資源使用強(qiáng)緩存疫铜,使用name-hash(非覆蓋式發(fā)布)解決靜態(tài)資源更新問題茂浮。
Q:配套的,前端靜態(tài)資源應(yīng)該如何組織壳咕?
A:搭配 Webpack 的Webpack_HTML-Plugin & 配置 output publicPath等席揽。
Q:配套的,自動化構(gòu)建 & 部署過程如何與 CDN 結(jié)合谓厘?
A:自動化構(gòu)建打包后幌羞,將產(chǎn)物傳輸?shù)綄?yīng)環(huán)境 URL 的CDN上。
Q:如何避免前端上線竟稳,影響未刷新頁面的用戶属桦?
A:使用name-hash方式組織靜態(tài)資源,先上線靜態(tài)資源住练,再上線HTML地啰。
Q:剛上線的版本發(fā)現(xiàn)有阻塞性 bug,如何做到秒級回滾讲逛,而非再次部署等 20 分鐘甚至更久亏吝?
A:HTML文件使用非覆蓋方式存儲在CDN上,搭建前端發(fā)布服務(wù)盏混,對 HTML 按版本等做緩存加工處理蔚鸥。當(dāng)需要回滾時,更改發(fā)布服務(wù)HTMl指向即可许赃。
Q: CDN 域名突然掛了止喷,如何實現(xiàn)秒級 CDN 降級修補(bǔ)而非再次全部業(yè)務(wù)重新部署一次?
A1: 將靜態(tài)資源傳輸?shù)蕉鄠€ CDN 上混聊,并開發(fā)一個加載Script的SDK集成到HTML中弹谁。當(dāng)發(fā)現(xiàn)CDN資源加載失敗時,逐步降級CDN域名。
A2:在前端發(fā)布服務(wù)中预愤,增加HTML文本處理環(huán)節(jié)沟于,如增加CDN域名替換,發(fā)生異常時植康,在發(fā)布服務(wù)中一鍵設(shè)置即可旷太。
Q:如何實現(xiàn)一個預(yù)發(fā)環(huán)境,除了前端資源外都是線上環(huán)境销睁,將變量控制前端環(huán)境內(nèi)供璧?
A:對靜態(tài)資源做加工,對HTML入口做小流量冻记。
Q:部署環(huán)節(jié)如何方便配套做 AB 測試等睡毒?
A:參見前端發(fā)布服務(wù)
Q:如何實現(xiàn)一套前端代碼,發(fā)布成多套環(huán)境產(chǎn)物檩赢?
A:使用環(huán)境變量吕嘀,將當(dāng)前環(huán)境、CDN贞瞒、CDN_HOST偶房、Version等注入環(huán)境變量中,構(gòu)建時消費(fèi) & 將產(chǎn)物上傳不同的CDN即可军浆。

其他

如果想深入學(xué)習(xí)前端部署棕洋,下面是一些學(xué)習(xí)建議。

  1. 學(xué)習(xí)負(fù)載均衡(要求:了解)乒融。
    學(xué)習(xí)和了解負(fù)載均衡的原理掰盘、都有哪些配置玩法。如參考大型網(wǎng)站架構(gòu)系列:負(fù)載均衡詳解

  2. 深入學(xué)習(xí) HTTP(要求:熟練掌握)
    如掌握常見的狀態(tài)碼赞季、常見的 Header 及其深度應(yīng)用愧捕、強(qiáng)緩存/協(xié)商緩存、HTTP2 的新增功能等等申钩。尤其HTTP 1.1 和 HTTP 2.0次绘。 推薦書籍:
    圖解HTTP
    HTTP 權(quán)威指南

  3. 深入學(xué)習(xí)前端工程化 (要求:精通)
    a. 了解前端工程化可以做什么,如 前端工程化:體系設(shè)計與實踐
    b. 掌握前端工程師的常見實踐原理 & 實操
    c. 深度學(xué)習(xí) Webpack Webpack 官方文檔

  4. 學(xué)習(xí)各種對前端靜態(tài)資源加工的各種方案(要求:掌握)

  5. 深度學(xué)習(xí)瀏覽器原理 (要求:精通)
    一些資料: 從瀏覽器多進(jìn)程到JS單線程撒遣,JS運(yùn)行機(jī)制最全面的一次梳理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邮偎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子义黎,更是在濱河造成了極大的恐慌禾进,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廉涕,死亡現(xiàn)場離奇詭異泻云,居然都是意外死亡艇拍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門宠纯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淑倾,“玉大人,你說我怎么就攤上這事征椒。” “怎么了湃累?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵勃救,是天一觀的道長。 經(jīng)常有香客問我治力,道長蒙秒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任宵统,我火速辦了婚禮晕讲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘马澈。我一直安慰自己瓢省,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布痊班。 她就那樣靜靜地躺著勤婚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涤伐。 梳的紋絲不亂的頭發(fā)上馒胆,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音凝果,去河邊找鬼祝迂。 笑死,一個胖子當(dāng)著我的面吹牛器净,可吹牛的內(nèi)容都是我干的型雳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掌动,長吁一口氣:“原來是場噩夢啊……” “哼四啰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粗恢,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柑晒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后眷射,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匙赞,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佛掖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了涌庭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芥被。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坐榆,靈堂內(nèi)的尸體忽然破棺而出拴魄,到底是詐尸還是另有隱情,我是刑警寧澤席镀,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布匹中,位于F島的核電站,受9級特大地震影響豪诲,放射性物質(zhì)發(fā)生泄漏顶捷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一屎篱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧交播,春花似錦、人聲如沸嚎尤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽次洼。三九已至,卻和暖如春卖毁,著一層夾襖步出監(jiān)牢的瞬間揖曾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工亥啦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炭剪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓翔脱,卻偏偏與公主長得像奴拦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子届吁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容