在開發元件庫或者外掛,經常會需要區分多種環境構建,從而實現:
- 提供各種體積版本:全量版、精簡版、基礎版等;
- 提供各種環境版本:web 版、nodejs 版等等;
- 提供各種規範版本:esm 規範版本、cjs 規範版本、UMD 規範版本等等。
那麼如何能夠方便實現上面功能呢?這種場景就適合使用 Feature Flags,在構建過程中,通過開關的啟用和關閉,對構建程式碼的過程進行動態設定,從而更好的實現 Tree Shaking。
Tree Shaking 是一種通過消除最終檔案中未使用的程式碼來優化體積的方法。
本文會從 Vue 原始碼(版本號:3.0.11)中使用的 Feature Flags 進行構建的過程開始介紹,然後通過簡單示例進行學習,最後介紹 rollup、webpack 和 Vite 中的實現。
本文程式碼地址:https://github.com/pingan8787...
一、什麼是 Feature Flags
Feature Flag(又名 Feature Toggle、Flip等)是一種允許控制線上功能開啟或者關閉的方式,通常會採取配置檔案的方式來控制。
可以理解為在程式碼中新增一個開關,當開關開啟,則邏輯會執行下去,否則不會執行,通常程式碼表現形式為 if
語句,舉個簡單示例:
const flags = true;
const test = () => flags && console.log('Hello Feature Flags');
當 flags
為 true
則執行輸出,否則不會。
現在我們想控制日誌會不會輸出,只需改變 flags
的值即可,test
方法邏輯不用修改。
? 可以將 Feature Flag 翻譯成特性標誌。
二、Vue 原始碼實現 Feature Flags
2.1 使用示例
從上一節對特性標誌的介紹後,大家應該對其有點理解,接下來從 Vue3 原始碼中看一個使用示例:
// packages/compiler-core/src/errors.ts
export function defaultOnWarn(msg: CompilerError) {
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
}
這裡的 __DEV__
就是一個 Feature Flag,當 __DEV__
值為 true
時,會輸出後面的日誌,否則不會輸出。
在 Vue3 原始碼中還存在很多其他特性標誌,比如:
__COMMIT__
__TEST__
__GLOBAL__
- ...
還有很多,有興趣的小夥伴可以在 Vue3 原始碼中找找。
2.2 如何定義特性標誌
上面只是帶大家看了下原始碼中如何使用,那麼接下來看看__DEV__
這些特性標誌是如何定義的。
Vue3 中使用了 @rollup/replace 依賴,實現構建時,替換檔案中目標字串內容,比如構建開發環境的包的過程中,將 __DEV__
替換為 true
。
還是以上面示例程式碼為例介紹:
// 本地開發環境 __DEV__ 為 true,經過 @rollup/replace 依賴打包後如下:
export function defaultOnWarn(msg: CompilerError) {
true && console.warn(`[Vue warn] ${msg.message}`)
}
// 生成環境中 __DEV__ 為 false,經過 @rollup/replace 依賴打包後如下:
export function defaultOnWarn(msg: CompilerError) {
}
構建後 defaultOnWarn
方法內的 console.warn
語句就被 Tree Shaking 移除掉了。
三、上手 Feature Flags
這一節通過將分別使用 rollup、webpack 和 Vite 實現三個 Feature Flags 的 Demo。其核心原理就是在構建階段的時候,已經明確的 Feature Flags 值的內容會被替換成具體的值,然後進行 Tree Shaking。
三個示例的全部程式碼可以到下面倉庫檢視:
首先我們先建立一個 index.js
檔案,輸入下面測試內容:
// index.js
const name = 'pingan8787';
const age = 18;
const featureFlags = () => {
console.warn(name)
__DEV__ && console.log(name)
}
featureFlags();
我們需要實現的目標是:當 __DEV__
變數的值為 true
時,打包後的 index.js
將不包含 __DEV__ && console.log(name)
這一行程式碼。
那麼開始看看如何實現:
3.1 rollup 實現
在 rollup 中,需要使用 @rollup/replace 包實現構建時替換文字,我們先安裝它:
npm install @rollup/plugin-replace --save-dev
然後在 rollup.config.js
中使用:
import replace from '@rollup/plugin-replace';
export default {
input: 'index.js',
output: {
file: './dist/index.js',
format: 'cjs'
},
plugins: [
replace({
__DEV__: true
})
]
};
接下來通過 rollup
打包命令,可以看到輸出內容如下:
const name = 'pingan8787';
const age = 18;
const featureFlags = () => {
console.warn(name)
__DEV__ && console.log(name)
}
featureFlags();
可以看到 __DEV__
為 true
時程式碼並沒有 Tree Shaking,再試試改成 false
,輸出如下:
'use strict';
const name = 'pingan8787';
const featureFlags = () => {
console.warn(name);
};
featureFlags();
這邊 __DEV__ && console.log(name)
就被移除了,實現 Tree Shaking。
照著相同原理,再看看 webpack 和 Vite 的實現:
3.2 webpack 實現
webpack 中自帶了 DefinePlugin
可以實現該功能,具體可以看 DefinePlugin 文件 ,下面看看 webpack.config.js
配置:
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
mode: 'production',
plugins: [
new webpack.DefinePlugin({
__DEV__: JSON.stringify(true),
})
],
};
因為這是使用 mode: 'production'
模式,所以打包出來的程式碼會壓縮:
(()=>{const n="pingan8787";console.warn(n),console.log(n)})();
可以看出 __DEV__
已經不存在,但 console.log(n)
還存在,這時候把 __DEV__
改成 false
再看看打包結果:
console.warn("pingan8787");
只剩下這句,其他都被 Tree Shaking 掉了。
3.3 Vite 實現
Vite 預設也是支援自定義全域性變數,實現該功能,可以看文件 define option。
通過 pnpm create vite
建立一個簡單 Vite 專案,並刪除多餘內容,並在 main.js
中加入我們的測試程式碼:
import { createApp } from 'vue'
import App from './App.vue'
const name = 'pingan8787';
const age = 18;
const featureFlags = () => {
console.warn(name)
__DEV__ && console.log(name)
}
featureFlags();
createApp(App).mount('#app')
並且在 vite.config.js
中設定 __DEV__
:
// vite.config.js
export default defineConfig({
plugins: [vue()],
define: {
__DEV__: true
}
})
然後執行 pnpm build
構建專案,可以看到壓縮後的程式碼還存在 __DEV__ && console.log(name)
。
接下來修改 __DEV__
的值為 false
,再重新打包,可以看到程式碼已經被 Tree Shaking 了:
到這裡我們就使用 rollup、webpack 和 Vite 分別實現了一遍 Feature Flags 了。
四、總結
本文通過簡單例子和 Vue3 原始碼,與大家介紹了 Feature Flags 的概念和簡單的實現,最後分別使用 rollup、webpack 和 Vite 分別實現了一遍 Feature Flags。
在實際業務開發中,我們可以通過設計各種 Feature Flags,讓程式碼能夠更好的進行 Tree Shaking。