需求
前端项目的环境变量一般在编译阶段,通过 .env 注入,在编译时就打包好了。但是当前端项目 docker 化后,某些变量只有在 docker 镜像启动时才会注入,此时需要做到一份镜像,随处可用。基于此,就产生了不同的一些方案
方案
- 后端增加配置接口,前端加载页面时请求接口获取配置。缺点是如果配置的接口在另一个域名下就没办法了。
- 接上1,由运维编写脚本,将配置的接口代理到当前域名的某个Path中。例如当前页面域名为 a.com 配置接口在 b.com/xx/config ,则由运维将 a.com/xx/config 代理到 b.com/xx/config 中。
- 前端 docker 启动时修改 public/config.js,在页面加载时请求配置文件,config.js 中将配置挂载到 window.config 中,这样完全无需运维及后端介入,由纯前端处理环境变量注入的问题,也是我们最终使用的方案,
处理过程
项目是 NextJs + react 构建,使用到了 SSR 的特性,所以有些地方会判断 typeof window !== 'undefined'
如果你用其他的架构方式,可以去掉这些判断
编译时的环境变量
.env.example.j2
NEXT_PUBLIC_API=/api/v1
NEXT_PUBLIC_URL={{app_url}}
NEXT_PUBLIC_API
为静态变量,在运行时也不会变化
NEXT_PUBLIC_URL
为动态变量,运行时会变,所以用 {{}}
包裹
每个公司的注入方式可能不一样,可以询问运维和其他项目的同学,如何获取到环境变量
之后在 docker-compose 中加入
environment:
- app_url=https://xxx.com
运维会编写一份脚本,将 .env.example.j2
用环境变量替换,此时,编译时的环境变量就处理完毕了
动态注入
public 中加入文件
config.js
const config = {
NEXT_PUBLIC_URL: '',
};
// 兼容 ssr
typeof window !== 'undefined' && (window._APP_CONFIG_ = config);
typeof global !== 'undefined' && (global._APP_CONFIG_ = config);
将 config.js 文件复制一份,命名为 config.example.js
const config = {
NEXT_PUBLIC_URL: '{{app_url}}',
};
// 兼容 ssr
typeof window !== 'undefined' && (window._APP_CONFIG_ = config);
typeof global !== 'undefined' && (global._APP_CONFIG_ = config);
这么做的原因是:
空的 config.js 保证在加载时不会 404,并且留空串方便使用时判断
config.example.js 是我们在替换完里面的变量后覆盖 config.js 用的。
声明类型
types.d.ts
// docker 运行时配置文件
interface GLOBAL_CONFIG {
NEXT_PUBLIC_URL?: string;
}
interface Window {
_APP_CONFIG_: GLOBAL_CONFIG;
}
export interface global {}
declare global {
var _APP_CONFIG_: GLOBAL_CONFIG;
}
目录中添加 entrypoint.sh 脚本文件
#!/bin/sh
err_exit() {
exit 1
}
config_app() {
[[ -z ${app_url} ]] && app_url=""
sed -e "s#{{app_url}}#${app_url}#g" ./app/public/config.example.js > ./app/public/config.js
}
config_app || err_exit "config app failed"
# 删除 nginx 默认文件
exec "${@}"
使用时的函数
import { GLOBAL_CONFIG } from 'pages/types';
export const getWindowConfig = (): GLOBAL_CONFIG => {
if (typeof window !== 'undefined') {
return window._APP_CONFIG_;
}
return {};
};
// 使用方式
// getWindowConfig()?.NEXT_PUBLIC_URL || process.env.NEXT_PUBLIC_URL
dockerfile 添加代码
ENTRYPOINT ["sh", "./entrypoint.sh"]
流程
docker image 启动 ->
运行 entrypoint.sh ->
使用系统环境变量替换 config.example.js ->
将 config.example.js 替换掉 config.js ->
前端加载是请求 config.js ->
判断 config.js 是否为空串,如果为空串则使用 .env 中的变量,如果不为则使用其
结尾
有点小优化,可以在 nginx 中对 config.js 开启协商缓存,这样不怎么影响加载的耗时
该方法除了新增环境变量时会比较繁琐,使用上还是很方便的