如何給框架和 JS 庫瘦身?

在開發組件庫或者插件,經常會需要區分多種環境構建,從而實現:

那麼如何能夠方便實現上面功能呢?這種場景就適合使用 Feature Flags,在構建過程中,通過開關的啓用和關閉,對構建代碼的過程進行動態設置,從而更好的實現 Tree Shaking。

Tree Shaking 是一種通過消除最終文件中未使用的代碼來優化體積的方法。

本文會從 Vue 源碼 (版本號:3.0.11) 中使用的 Feature Flags 進行構建的過程開始介紹,然後通過簡單示例進行學習,最後介紹 rollup、webpack 和 Vite 中的實現。

一、什麼是 Feature Flags

Feature Flag(又名 Feature Toggle、Flip 等) 是一種允許控制線上功能開啓或者關閉的方式,通常會採取配置文件的方式來控制。

http://fex.baidu.com/blog/2014/07/feature-flag/

可以理解爲在代碼中添加一個開關,當開關開啓,則邏輯會執行下去,否則不會執行,通常代碼表現形式爲 if語句,舉個簡單示例:

const flags = true;
const test = () => flags && console.log('Hello Feature Flags');

flagstrue則執行輸出,否則不會。現在我們想控制日誌會不會輸出,只需改變 flags的值即可,test方法邏輯不用修改。

😺 可以將 Feature Flag 翻譯成**「特性標誌」**。****

後面所有示例代碼地址:https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Vue/Source/FeatureFlags/

二、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 源碼中還存在很多其他特性標誌,比如:

還有很多,有興趣的小夥伴可以在 Vue3 源碼中找找。

2.2 如何定義特性標誌

上面只是帶大家看了下源碼中如何使用,那麼接下來看看__DEV__這些特性標誌是如何定義的。Vue3 中使用了 [@rollup/replace](https://github.com/rollup/plugins/tree/master/packages/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](https://github.com/rollup/plugins/tree/master/packages/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](https://github.com/vitejs/vite/blob/a4133c073e640b17276b2de6e91a6857bdf382e1/src/node/config.ts#L72-L76) 。通過 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。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/d5SONpkpfd5JWV-AjyEjWQ