SPA 沒(méi)有運(yùn)行時(shí)環(huán)境變量的痛點(diǎn)
目前我的絕大部分的項(xiàng)目都是一個(gè)前后端分離的方式開(kāi)發(fā)的屉符。其中前端基本都是用 create-react-app
創(chuàng)建出來(lái)的標(biāo)準(zhǔn)的 react 的 spa 應(yīng)用剧浸。這種 spa 在部署是將所有的 js 和 css 打包成一個(gè)或多個(gè)文件然后用 serve
或者其他類(lèi)似的 http server 以靜態(tài)文件的形式對(duì)外提供服務(wù),但是這種前端靜態(tài)文件話的應(yīng)用沒(méi)有 nodejs 的支持筑煮,沒(méi)辦法使用 process.env
這樣的運(yùn)行時(shí)注入環(huán)境變量的功能辛蚊。
目前 create-react-app
提供了一個(gè)編譯運(yùn)行時(shí)環(huán)境變量的方案,因?yàn)樵?build
的時(shí)候是有 nodejs
支持的真仲,通過(guò) REACT_APP_API_URL=http://xxx.com yarn run build
的方式在編譯 spa 的時(shí)候注入環(huán)境變量袋马。那么編譯時(shí)的環(huán)境變量能不能解決問(wèn)題呢?看情況了...可以做一個(gè)簡(jiǎn)單的對(duì)比秸应。
要知道我們通常要把什么樣子的環(huán)境變量注入到 spa 中虑凛。額,我這里的需求很有限软啼,為了讓前后端一起運(yùn)作桑谍,我所需要的環(huán)境變量就是后端 API 的入口。對(duì)于部署流程簡(jiǎn)單到之后生產(chǎn)環(huán)境且生產(chǎn)環(huán)境固定(尤其是后端生產(chǎn)環(huán)境 IP祸挪、域名固定)的情況锣披,直接在編譯時(shí)將后端的入口寫(xiě)死注入就行了。但如果有多個(gè)環(huán)境(staging)的需求就不適用了,假如沒(méi)有運(yùn)行時(shí)環(huán)境變量的支持為不同的環(huán)境提供不同的入口只能重新編譯應(yīng)用并注入不同的變量雹仿。
有沒(méi)有需求在應(yīng)用運(yùn)行時(shí)修改我們的環(huán)境變量增热。很明顯運(yùn)行時(shí)的環(huán)境變量支持通過(guò)重啟就能修改環(huán)境變量的功能,如果有這種靈活修改環(huán)境變量的情況胧辽,編譯時(shí)環(huán)境變量很明顯也不能滿足峻仇。
在編譯時(shí)對(duì)代碼選擇和裁剪。很明顯邑商,這個(gè)是最應(yīng)該使用編譯時(shí)環(huán)境變量的地方了摄咆。
說(shuō)白了,其實(shí)不同時(shí)期的環(huán)境變量的作用是不一樣的人断。兩者不可能做到相互替代吭从,在 [1]
[2]
兩個(gè)場(chǎng)景都是使用運(yùn)行時(shí)環(huán)境變量比較舒服的地方,采用編譯時(shí)的環(huán)境變量實(shí)在是不太方便含鳞。下面就介紹一下目前讓 spa 應(yīng)用支持運(yùn)行時(shí)環(huán)境變量的方法影锈,這里還是以 create-react-app
的模板為示例。
全局配置 + Docker 化部署
前端沒(méi)有 process.env
這樣的東西蝉绷,我們只能用 javascript 的全局變量模擬鸭廷。在將這個(gè)打包好的 spa 運(yùn)行起來(lái)的時(shí)候,我們需要利用 shell 腳本生成這個(gè) config.js 文件熔吗,讓它把必要的環(huán)境變量翻譯成全局變量辆床。然后讓默認(rèn)的入口 html 文件引入這個(gè)全局變量文件。
首先桅狠,我們需要一段 shell 腳本讼载,把環(huán)境變量翻譯成 config.js
文件:
#!/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 "$@"
如果我們提供這樣的環(huán)境變量
export REACT_APP_API_PREFIX=http://petstore-backend.example.com
export CONFIG_VARS=REACT_APP_API_PREFIX
那么所生成的 config.js
文件是這個(gè)樣子的:
window.__env = {
'REACT_APP_API_PREFIX': 'http://petstore-backend.example.com'
}
然后,我們需要在 原來(lái)的 index.html
模板文件中引入這個(gè)我們生成的 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>
這樣中跌,我們就擁有了一個(gè) window.__env
的全局對(duì)象咨堤,它包含了所有的運(yùn)行時(shí)環(huán)境變量。我們可以以如下的方式使用它:
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
全局變量 - 在需要的地方引用其中的變量即可
當(dāng)然漩符,這種依賴(lài) shell 生成 config.js
的方案只有我們將 spa 打包好的之后才會(huì)使用一喘,為了更好的使用這個(gè) shell 我們可以采用 docker 化的方式把其啟動(dòng)流程以 entrypoint 的方式固化在應(yīng)用的啟動(dòng)流程中。SocialEngine/docker-nginx-spa 就實(shí)現(xiàn)了這個(gè)方案嗜暴,是一個(gè)很好的用 base image凸克。如果我們需要?jiǎng)?chuàng)建一個(gè)支持運(yùn)行時(shí)環(huán)境變量的 create-react-app spa 的時(shí)候,首先按照上面的步驟修改 public/index.html
并且用 window.__env
作為環(huán)境變量使用闷沥。然后提供一個(gè)繼承自 SocialEngine/docker-nginx-spa
的 Dockerfile
即可萎战。
FROM socialengine/nginx-spa
COPY build/ /app
其中 build/
是 create-react-app
編譯生成靜態(tài)文件的默認(rèn)目錄。然后打包運(yùn)行這個(gè)應(yīng)用的方式如下:
$ 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
當(dāng)然舆逃,我們本地開(kāi)發(fā)環(huán)境不用這么麻煩蚂维。只需要在 public/
目錄下自己創(chuàng)建一個(gè) config.js
然后把開(kāi)發(fā)需要的環(huán)境變量塞進(jìn)去就可以了戳粒。在 docker 化后,entrypoint 觸發(fā)的命令會(huì)自動(dòng)覆蓋這個(gè) config.js 文件鸟雏。
這里 是一個(gè)樣例項(xiàng)目享郊。
相關(guān)資料
更多內(nèi)容請(qǐng)見(jiàn) aisensiy.github.io