需求:目前项目的环境变量都是配置在工程 .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
即可。
虽然这种方法能解决打包后修改环境变量的需求,但是还是有很多缺点。比如
- 每次打开页面都需要发起一次网络请求去查询环境变量。
- 不好控制在环境变量网络请求结束后再去加载页面。
- 不够优雅,每次需要环境变量时都需要调用一次
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. 总结
方案一虽然能解决问题,但是还是存在很多缺点
方案二相比方案一就好了很多,比如:
- 不用在前端项目的 public 文件夹中在维护一份环境变量增加心智负担
- 还能在页面加载前注入环境变量,使项目初始化就能加载注入的变量。
- 对项目的修改也只需修改
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;