vite 项目打包后修改环境变量的方案

1,432 阅读3分钟

需求:目前项目的环境变量都是配置在工程 .env 下,只要每次修改环境变量或者有多个版本时就需要重新打包部署,严重增加前端维护成本,需要对其进行配置化,即打包后还能修改环境变量,后续线上版本需要修改环境变量就不需要前端维护。

1. 方案一

思路:public 中维护一份生产环境的环境变量(打包后会跟随源码上传至服务器),前端通过网络请求获取变量后注入到项目中

1.1 维护一份用于线上环境配置的环境变量

env.json

{
  "VITE_API_URL": "http://192.168.193.83:9280",
  "VITE_API_REPORT_PORT": "1106"
}

1.2 封装网络请求获取环境变量

rewrite-env.js

import request from "@/utils/request";
const BASE_URL = import.meta.env.BASE_URL;
let reqPromise;

/**
 * 获取环境变量
 */
export function getEnv() {
  if (!reqPromise) {
    reqPromise = new Promise((resolve, reject) => {
      request
        .get(BASE_URL + "json/env.json", {
          baseURL: "",
        })
        .then((res) => {
          console.log({ res });
          resolve(res.data ?? []);
        })
        .catch((e) => {
          reject(e);
        });
    });
  }
  return reqPromise;
}

最后在需要使用环境变量的地方调用 getEnv 即可。

虽然这种方法能解决打包后修改环境变量的需求,但是还是有很多缺点。比如

  1. 每次打开页面都需要发起一次网络请求去查询环境变量。
  2. 不好控制在环境变量网络请求结束后再去加载页面。
  3. 不够优雅,每次需要环境变量时都需要调用一次 getEnv

2 方案二

在 build 打包时,生成一份 js 脚本将环境变量赋值到 window 全局变量中,最后注入到 html 中。

2.1 package.json 脚本配置

"build": "vite build && node ./bin/inject-env-to-window.js"

2.2 inject-env-to-window.js 代码

import fs from "fs-extra";
const CONFIG_NAME = "__PRODUCTION____APP__CONF__";
const CONFIG_FILE_NAME = "_app.config.js";
const OUTPUT_DIR = "dist";
import path from "path";
import dotenv from "dotenv";

/**
 * 获取用户根目录
 * @param dir file path
 */
export function getRootPath(...dir) {
  return path.resolve(process.cwd(), ...dir);
}

/**
 * 获取以指定前缀开头的环境变量
 * @param match prefix
 * @param confFiles ext
 */
function getEnvConfig(
  match = "VITE_",
  confFiles = [".env", ".env.production"]
) {
  let envConfig = {};
  confFiles.forEach((item) => {
    try {
      const env = dotenv.parse(
        fs.readFileSync(path.resolve(process.cwd(), item))
      );
      envConfig = { ...envConfig, ...env };
    } catch (e) {
      console.error(`解析错误:${item}`, e);
    }
  });
  const reg = new RegExp(`^(${match})`);
  Object.keys(envConfig).forEach((key) => {
    if (!reg.test(key)) {
      Reflect.deleteProperty(envConfig, key);
    }
  });
  return envConfig;
}

function createConfig(params) {
  const { configName, config, configFileName } = params;
  try {
    const windowConf = `window.${configName}`;

    // 确保变量不会被修改
    const configStr = `${windowConf}=${JSON.stringify(config)};
      Object.freeze(${windowConf});
      Object.defineProperty(window, "${configName}", {
        configurable: false,
        writable: false,
      });
    `.replace(/\s/g, "");

    // 拼接新的输出根目录地址
    const filePath = `${OUTPUT_DIR}/`;

    // 创建根目录
    fs.mkdirp(getRootPath(filePath));
    fs.writeFileSync(getRootPath(filePath + configFileName), configStr);

    console.log(`✨ 配置文件构建成功:`);
    console.log(filePath + "\n");
  } catch (error) {
    console.log("配置文件配置文件打包失败:\n" + error);
  }
}

const runBuild = async () => {
  try {
    const config = getEnvConfig();
    createConfig({
      config,
      configName: CONFIG_NAME,
      configFileName: CONFIG_FILE_NAME,
    });
    console.log(`环境变量注入成功 - 构建成功!`);
  } catch (error) {
    console.log("虚拟构建错误:\n" + error);
    process.exit(1);
  }
};

runBuild();

2.3 执行打包命令

最终会在打包后的 dist 文件夹下生成一份 _app.config.js

window.__PRODUCTION____APP__CONF__ = {
  VITE_APP_NAME: "智汇金市",
  VITE_API_PREFIX: "/marketing/api",
  VITE_API_URL: "http://192.168.193.83:9280",
  VITE_APP_SSO_HOST: "http://localhost:8888",
  VITE_APP_SSO_ID: "1187303628294164583",
  VITE_BASE_URL: "/marketing/",
};
Object.freeze(window.__PRODUCTION____APP__CONF__);
Object.defineProperty(window, "__PRODUCTION____APP__CONF__", {
  configurable: false,
  writable: false,
});

2.4 通过 vite-plugin-html 注入到 html 中

createHtmlPlugin({
  minify: isBuild,
  inject: {
    tags: isBuild
      ? [
          {
            tag: "script",
            attrs: {
              src: `${env.VITE_BASE_URL}/dist/_app.config.js`,
            },
          },
        ]
      : [],
  },
});

3. 总结

方案一虽然能解决问题,但是还是存在很多缺点

方案二相比方案一就好了很多,比如:

  1. 不用在前端项目的 public 文件夹中在维护一份环境变量增加心智负担
  2. 还能在页面加载前注入环境变量,使项目初始化就能加载注入的变量。
  3. 对项目的修改也只需修改 setting.js 一个文件即可(在 setting.js 中导出环境变量)。
const isProduction = import.meta.env.MODE === "production";
const env = isProduction ? window.__PRODUCTION____APP__CONF__ : import.meta.env;

// 接口地址前缀
export const API_BASE_PREFIX = env.VITE_API_PREFIX;

export const API_BASE_URL = env.VITE_API_URL;

// 项目名称
export const PROJECT_NAME = env.VITE_APP_NAME;

// 单点服务器
export const SSO_HOST = env.VITE_APP_SSO_HOST;

// 单点客户端id标识
export const SSO_CLIENT_ID = env.VITE_APP_SSO_ID;

// context-path
export const BASE_URL = env.VITE_BASE_URL;