最近攻克了一个以前部署 single-page-app 的一个痛点:支持在运行时环境变量。这里讲述一下问题以及目前的解决方案。javascript
目前个人绝大部分的项目都是一个先后端分离的方式开发的。其中前端基本都是用 create-react-app
建立出来的标准的 react 的 spa 应用。这种 spa 在部署是将全部的 js 和 css 打包成一个或多个文件而后用 serve
或者其余相似的 http server 以静态文件的形式对外提供服务,可是这种前端静态文件话的应用没有 nodejs 的支持,没办法使用 process.env
这样的运行时注入环境变量的功能。css
目前 create-react-app
提供了一个编译运行时环境变量的方案,由于在 build
的时候是有 nodejs
支持的,经过 REACT_APP_API_URL=http://xxx.com yarn run build
的方式在编译 spa 的时候注入环境变量。那么编译时的环境变量能不能解决问题呢?看状况了...能够作一个简单的对比。html
要知道咱们一般要把什么样子的环境变量注入到 spa 中。额,我这里的需求颇有限,为了让先后端一块儿运做,我所须要的环境变量就是后端 API 的入口。对于部署流程简单到以后生产环境且生产环境固定(尤为是后端生产环境 IP、域名固定)的状况,直接在编译时将后端的入口写死注入就好了。但若是有多个环境(staging)的需求就不适用了,假如没有运行时环境变量的支持为不一样的环境提供不一样的入口只能从新编译应用并注入不一样的变量。前端
有没有需求在应用运行时修改咱们的环境变量。很明显运行时的环境变量支持经过重启就能修改环境变量的功能,若是有这种灵活修改环境变量的状况,编译时环境变量很明显也不能知足。java
在编译时对代码选择和裁剪。很明显,这个是最应该使用编译时环境变量的地方了。node
说白了,其实不一样时期的环境变量的做用是不同的。二者不可能作到相互替代,在 [1]
[2]
两个场景都是使用运行时环境变量比较舒服的地方,采用编译时的环境变量实在是不太方便。下面就介绍一下目前让 spa 应用支持运行时环境变量的方法,这里仍是以 create-react-app
的模板为示例。react
前端没有 process.env
这样的东西,咱们只能用 javascript 的全局变量模拟。在将这个打包好的 spa 运行起来的时候,咱们须要利用 shell 脚本生成这个 config.js 文件,让它把必要的环境变量翻译成全局变量。而后让默认的入口 html 文件引入这个全局变量文件。ios
首先,咱们须要一段 shell 脚本,把环境变量翻译成 config.js
文件:nginx
#!/bin/bash if [[ $CONFIG_VARS ]]; then SPLIT=$(echo $CONFIG_VARS | tr "," "\n") ARGS= for VAR in ${SPLIT}; do ARGS="${ARGS} -v ${VAR} " done JSON=`json_env --json $ARGS` echo " ==> Writing ${CONFIG_FILE_PATH}/config.js with ${JSON}" echo "window.__env = ${JSON}" > ${CONFIG_FILE_PATH}/config.js fi exec "$@"
若是咱们提供这样的环境变量git
export REACT_APP_API_PREFIX=http://petstore-backend.example.com export CONFIG_VARS=REACT_APP_API_PREFIX
那么所生成的 config.js
文件是这个样子的:
window.__env = { 'REACT_APP_API_PREFIX': 'http://petstore-backend.example.com' }
而后,咱们须要在 原来的 index.html
模板文件中引入这个咱们生成的 config.js
文件:
<!doctype html> <html lang="en"> <head> ... </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <div id="root"></div> <script type="text/javascript" src="config.js"></script> </body> </html>
这样,咱们就拥有了一个 window.__env
的全局对象,它包含了全部的运行时环境变量。咱们能够以以下的方式使用它:
axios.defaults.adapter = httpAdapter; let baseUrl; let env = window.__env || {}; // 1 if (process.env.NODE_ENV === 'test') { baseUrl = 'http://example.com'; } else if (process.env.NODE_ENV === 'development') { baseUrl = env.REACT_APP_API_PREFIX || 'http://localhost:8080'; // 2 } else { baseUrl = env.REACT_APP_API_PREFIX; } const fetcher = axios.create({ baseURL: baseUrl, headers: { 'Content-Type': 'application/json' } });
window.__env
全局变量固然,这种依赖 shell 生成 config.js
的方案只有咱们将 spa 打包好的以后才会使用,为了更好的使用这个 shell 咱们能够采用 docker 化的方式把其启动流程以 entrypoint 的方式固化在应用的启动流程中。SocialEngine/docker-nginx-spa 就实现了这个方案,是一个很好的用 base image。若是咱们须要建立一个支持运行时环境变量的 create-react-app spa 的时候,首先按照上面的步骤修改 public/index.html
而且用 window.__env
做为环境变量使用。而后提供一个继承自 SocialEngine/docker-nginx-spa
的 Dockerfile
便可。
FROM socialengine/nginx-spa COPY build/ /app
其中 build/
是 create-react-app
编译生成静态文件的默认目录。而后打包运行这个应用的方式以下:
$ yarn run build $ docker build -t spa-app . $ docker run -e CONFIG_VARS=REACT_APP_API_PREFIX -e REACT_APP_API_PREFIX=http://petstore-backend.example.com -p 3000:80 spa-app
固然,咱们本地开发环境不用这么麻烦。只须要在 public/
目录下本身建立一个 config.js
而后把开发须要的环境变量塞进去就能够了。在 docker 化后,entrypoint 触发的命令会自动覆盖这个 config.js 文件。
这里 是一个样例项目。
更多内容请见 aisensiy.github.io