使用Feature Matrix來構建應用
起了一個吹牛逼的標題,但其實做的事情非常簡單。
背景
在我們團隊多數的產品中,需要做灰度發布或者說小流量分流的時候,通常是以機器或者以目錄為單元分流,同時進行多次構建,利用不同的參數構建出不同的結果,比如:
npm run build -- --target=stable# /assets# app.xxx.js# index.htmlnpm run build -- --target=insiders# /assets# app.yyy.js# index.html
特點是每一次構建都會產出一樣的HTML文件(index.html
),隨後將這2份產物送到不同的伺服器上,或者送到同一伺服器的不同文件夾中,再進行分流,基本上就是這樣的:
這種方案其實存在著一些不容易被感知到的缺陷:
- 要做多次構建,多次構建過程中的緩存利用等就會成為問題,可能拖慢速度。
- 多次構建放在多個產出目錄下,部署的時候要將目錄與線上的機器/目錄對應上,這裡引入了一個故障點(POF),如果上錯了對應關係怎麼辦?
- 如果按機器分流,不同流量間的PV量是不一致的,要麼預測PV的分布來準備不同數量的機器,要麼就看到有些機器忙死有些閑死。
除了這些以外,我們面臨著一個更糟糕的問題:使用Cookie進行分流的時候,沒有Cookie時請求一個具體的文件(如app.xxx.js
)時,分流系統無法確切地找到這個文件(在哪台機器/哪個文件夾上)。
而我們使用的Sentry異常監控是需要下載Source Maps的,同樣Firefox在下載Source Maps時也不會帶上Cookie,這導致幾乎所有的Source Maps下載都宣告失敗,我們無法定位和分析問題。
解決方案
為了解決這一問題,我們決定把不同流量的構建產物直接放在一起。而為了能放在一起,我們亟待處理的問題就成了:index.html
這個文件的重複怎麼辦。
當然這是一個容易解決的問題,直接按照流量名來生成不同的文件就行,然後讓分流直接分到不同的.html
文件上,即變成這樣子:
所以我們的問題變成了,如何用一個統一的模式,來構建出上圖中的全部內容。
技術實現
首先,我們發現webpack實際可以接受一個數組作為配置,所以我們就用一個文件來生成一個配置數組。
我們定義了一種模式來支持不同的開關,所有的開關被統一命名到features.flagName
下,然後我們就可以通過編寫一個features.js
來定義流量以及對應的開關:
// features.jsexports.stable = { allowEditOfUsername: false, githubSignIn: false};exports.insiders = { allowEditOfUsername: true, githubSignIn: true};exports.dev = { allowEditOfUsername: true, githubSignIn: true};
但是我們並不希望所有用到開關的地方都需要寫import features from features;
這樣的代碼,同時即便寫了其實也沒辦法選擇到正確的流量。
因此我們選擇了DefinePlugin來完成這一任務,根據當前的流量來生成一系列的features.flagName
常量,對源碼進行替換。一個大概的代碼就是:
const getFeatureFlagDefinitions = buildTarget => { const flags = features[buildTarget]; return Object.entries(flags).reduce( (output, [key, value]) => { return { ...output, [features. + key]: JSON.stringify(value) }; }, {} );};
在獲得所有的流量名稱後,用這個方法來創建webpack配置:
const createWebpackConfig = buildTarget => { const featureFlags = getFeatureFlagDefinitions(buildTarget); return { ..., plugins: [ ..., new DefinePlugin(featureFlags), new WebpackHtmlPlugin({filename: buildTarget + .html}) ] };};const featureNames = Object.keys(features);module.exports = featureNames.map(createWebpackConfig);
具體效果
在構建的入口處,我們提供了可視化的當前流量-功能的對應關係:
同時一次構建2個流量並沒有導致構建時間的增長,大致分析了一下,這是因為:
babel-loader
、eslint-loader
等的cache
配置存在,同一次構建過程中不會重複地編譯。- webpack在接受了多個配置對象時,似乎會使用多線程的形式進行構建,因此多核的環境下並沒有嚴重損失。
一些細節
首先有一個奇怪的小細節,cssnano
必須安裝@next
的版本。當webpack進行多配置構建時,似乎會進入strict mode,而普通版本的cssnano依賴uniqid
這個庫有一個全局變數泄露,會導致構建失敗。
在設計之初,Feature Matrix的構建有一個額外的功能,當2個流量上的flag完全相同時,僅構建一次,隨後通過多個 WebpackHtmlPlugin
實例來生成.html
文件,以節省構建的成本。但後續發現在代碼中往往不僅僅用於flag,也需要流量的名稱,如提供給Sentry監控平台來區分當前用戶所在的流量。因此節省重複構建的功能在後續去除了。
推薦閱讀:
※Hello Webpack.1
※推薦閱讀 - 第16期
※webpack 源碼解讀(1)--源碼結構
※深入理解 webpack 文件打包機制
※React-router4和webpack中output的publicPath相關關係
TAG:webpack |