為 Single Page App 提供運(yùn)行時(shí)環(huán)境變量

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ì)比秸应。

  1. 要知道我們通常要把什么樣子的環(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)用并注入不同的變量雹仿。

  2. 有沒(méi)有需求在應(yīng)用運(yùn)行時(shí)修改我們的環(huán)境變量增热。很明顯運(yùn)行時(shí)的環(huán)境變量支持通過(guò)重啟就能修改環(huán)境變量的功能,如果有這種靈活修改環(huán)境變量的情況胧辽,編譯時(shí)環(huán)境變量很明顯也不能滿足峻仇。

  3. 在編譯時(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'
  }
});
  1. 直接在文件中引入 window.__env 全局變量
  2. 在需要的地方引用其中的變量即可

當(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-spaDockerfile 即可萎战。

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)資料

  1. create-react-app
  2. compile-time-vs-runtime
  3. serve
  4. SocialEngine/docker-nginx-spa

更多內(nèi)容請(qǐng)見(jiàn) aisensiy.github.io

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孝鹊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展蒂,老刑警劉巖又活,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锰悼,居然都是意外死亡柳骄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)箕般,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耐薯,“玉大人,你說(shuō)我怎么就攤上這事丝里∏酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵杯聚,是天一觀的道長(zhǎng)臼婆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)幌绍,這世上最難降的妖魔是什么颁褂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮傀广,結(jié)果婚禮上颁独,老公的妹妹穿的比我還像新娘。我一直安慰自己伪冰,他們只是感情好誓酒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著糜值,像睡著了一般丰捷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寂汇,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天病往,我揣著相機(jī)與錄音,去河邊找鬼骄瓣。 笑死停巷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畔勤,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蕾各,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了庆揪?” 一聲冷哼從身側(cè)響起式曲,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缸榛,沒(méi)想到半個(gè)月后吝羞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡内颗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年钧排,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片均澳。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恨溜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出找前,到底是詐尸還是另有隱情糟袁,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布纸厉,位于F島的核電站系吭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颗品。R本人自食惡果不足惜肯尺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躯枢。 院中可真熱鬧则吟,春花似錦、人聲如沸锄蹂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)得糜。三九已至敬扛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間朝抖,已是汗流浹背啥箭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留治宣,地道東北人急侥。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓猴贰,卻偏偏與公主長(zhǎng)得像芭商,于是被迫代替她去往敵國(guó)和親蒂培。 傳聞我的和親對(duì)象是個(gè)殘疾皇子积糯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)铝宵,斷路器打掘,智...
    卡卡羅2017閱讀 134,707評(píng)論 18 139
  • 寫(xiě)在開(kāi)頭 先說(shuō)說(shuō)為什么要寫(xiě)這篇文章, 最初的原因是組里的小朋友們看了webpack文檔后, 表情都是這樣的: (摘...
    Lefter閱讀 5,297評(píng)論 4 31
  • 無(wú)意中看到zhangwnag大佬分享的webpack教程感覺(jué)受益匪淺,特此分享以備自己日后查看鹏秋,也希望更多的人看到...
    小小字符閱讀 8,178評(píng)論 7 35
  • 分享一本書(shū)拼岳,日本作家的《整理是一切的開(kāi)始》,書(shū)的封面上最醒目的一句話是况芒,省下更多時(shí)間惜纸,花在自己喜歡的人和事上!不懂...
    雪穎清葙閱讀 2,207評(píng)論 0 3
  • 敢跟我爭(zhēng)寵的女人都得死绝骚。 華妃娘娘的霸氣耐版,即便《甄嬛傳》熱播過(guò)去很久,依舊能夠讓觀眾脊背發(fā)涼压汪,尤其是那余光一撇的眼...
    媽小咪閱讀 648評(píng)論 1 2