前端项目 docker 化后动态注入环境变量

371 阅读3分钟

需求

前端项目的环境变量一般在编译阶段,通过 .env 注入,在编译时就打包好了。但是当前端项目 docker 化后,某些变量只有在 docker 镜像启动时才会注入,此时需要做到一份镜像,随处可用。基于此,就产生了不同的一些方案

方案

  1. 后端增加配置接口,前端加载页面时请求接口获取配置。缺点是如果配置的接口在另一个域名下就没办法了。
  2. 接上1,由运维编写脚本,将配置的接口代理到当前域名的某个Path中。例如当前页面域名为 a.com 配置接口在 b.com/xx/config ,则由运维将 a.com/xx/config 代理到 b.com/xx/config 中。
  3. 前端 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 开启协商缓存,这样不怎么影响加载的耗时

该方法除了新增环境变量时会比较繁琐,使用上还是很方便的