场景

  • 当一套代码,需要发布到两个环境( A环境B环境 )中时,A环境 下需要系统管理页面,B环境 下不需要系统管理页面。这时,我们可以通过后端返回的菜单控制。

  • 但是如果 A环境B环境 的登录页面也不一样呢?这时,我们就可能需要模板语法来控制。

  • 需要自定义一个 vite 插件,在打包前,读取环境变量,然后使用 blueimp-tmpl 的模板语法对代码进行判断并替换。

项目地址

实践

安装依赖

  • blueimp-tmpl 模板语法

    1
    npm install blueimp-tmpl
  • cross-env 跨环境执行命令

    1
    npm install cross-env -D

获取环境变量

  • package.json 文件中添加 scripts 脚本

    1
    2
    3
    4
    5
    6
    7
    8
    "scripts": {
    "dev": "npm run dev:A",
    "dev:A": "cross-env platform=A vite",
    "dev:B": "cross-env platform=B vite",
    "build": "npm run build:A",
    "build:A": "npx rimraf dist && cross-env platform=A vite build",
    "build:B": "npx rimraf dist && cross-env platform=B vite build",
    },
  • platform=Aplatform=B 是自定义变量。只要是 xxx=xxx 这种格式的变量,都可以通过 process.env.xxx 拿到。

  • vite.config.ts 文件中获取环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';

    // https://vite.dev/config/
    export default () => {
    const platform = process.env.platform; // 获取环境变量
    console.log('platform', platform);

    return defineConfig({
    plugins: [react()],
    });
    };
  • 执行 npm run dev:A 或者 npm run dev:B,可以在 启动窗口(终端) 中看到打印的 platform 变量值

使用 Vite 插件

  • 在项目根目录下创建 vite-plugin-blueimp-tmpl.ts 文件,添加下面内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    // @ts-ignore
    import tmpl from 'blueimp-tmpl';
    import { Plugin } from 'vite';

    interface Options {
    /** 匹配内容 */
    match?: RegExp;
    /** 匹配文件 */
    test?: RegExp;
    /** 模板数据 */
    data: Record<string, any>;
    }

    /**
    * 模板插件
    * - 匹配文件中的 //tmpl 内容
    * - 使用 blueimp-tmpl 进行模板编译 {% ... %} 中的表达式
    * - 通过 o.xxx 访问 data 对象中的数据
    * @example //tmpl {% o.name %}
    * @example
    * //tmpl {% if(o.platform == 'A') { %}
    * xxx
    * //tmpl {% } %}
    */
    const vitePluginBlueimpTmpl = (props: Options): Plugin => {
    const { match = /\/\/tmpl/g, test = /src.+\.ts(x)?/, data = {} } = props;

    return {
    name: 'vite-plugin-blueimp-tmpl', // 插件名称
    enforce: 'pre', // 在 Vite 核心插件之前调用该插件, 打包之前运行
    transform: (code: string, id: string) => {
    // id 是文件路径, code 是文件内容
    if (test.test(id) && code.match(match)) {
    // 会将 //tmpl 替换为空
    const str = code.replace(match, '');

    /**
    * 使用 blueimp-tmpl 进行模板编译,
    * - https://www.npmjs.com/package/blueimp-tmpl#api
    * - 解析 {% ... %} 中的表达式, 并将 data 对象中的数据传入模板中
    */
    const tmplstr = tmpl(str)(data);

    console.log(id); // 打印出被处理的文件路径
    console.log(tmplstr); // 打印出处理后的文件内容

    return tmplstr;
    }
    },
    };
    };

    export default vitePluginBlueimpTmpl;
  • vite.config.ts 文件中添加插件,并传入 platform 环境变量。(需要将 vitePluginBlueimpTmpl 放在插件最前面,方便拿到未编译的代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    import vitePluginBlueimpTmpl from './vite-plugin-blueimp-tmpl';

    // https://vite.dev/config/
    export default () => {
    const platform = process.env.platform; // 获取环境变量
    console.log('platform', platform);

    return defineConfig({
    plugins: [vitePluginBlueimpTmpl({ data: { platform } }), react()],
    });
    };

使用模板语法

  • src/App.tsx 文件中添加下面内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { useEffect } from 'react';
    import './App.css';

    function App() {
    useEffect(() => {
    //tmpl {% if(o.platform == 'A') { %}
    console.log('A环境');
    //tmpl {% } %}

    //tmpl {% if(o.platform == 'B') { %}
    console.log('B环境');
    //tmpl {% } %}
    }, []);

    return (
    <>
    {/* //tmpl {% if(o.platform == 'A') { %} */}
    <div>我是A环境</div>
    {/* //tmpl {% } %} */}

    {/* //tmpl {% if(o.platform == 'B') { %} */}
    <div>我是B环境</div>
    {/* //tmpl {% } %} */}
    </>
    );
    }

    export default App;
  • 此时启动项目,访问页面之后,在 启动窗口(终端) 中可以看到编译后的代码(必须访问页面后终端才会打印!!!,因为 vite 是按需编译的)

部署

  • 最后在 CICD 流水线上只需要添加 npm run build:A 或者 npm run build:B 命令,就可以实现环境切换了