入職新公司以來缕题,第一個月接手vue項目胖腾,第二個月接手angularjs項目咸作,第三個月加入react重構(gòu)項目记罚。心生感嘆:業(yè)務驅(qū)動式學習是一種高效率的學習方式,保持好奇心末早,在業(yè)務中快速成長!
新項目中在package.json中有一個proxy字段焙糟,這是我從來沒接觸過的穿撮,因此就有了此文的誕生痪欲,我使用create-react-app 新建了一個最原始狀態(tài)的項目业踢,對proxy字段與create-react-app之間的糾葛展開了學習。
在npm-configuration中瞬沦,對proxy有如下解釋:
默認值為null逛钻,類型為url曙痘,一個為了發(fā)送http請求的代理立肘。如果HTTP__PROXY或者http_proxy環(huán)境變量已經(jīng)設置好了谅年,那么proxy設置將被底層的請求庫實現(xiàn)。
這個proxy字段目前我只了解到可以與create-react-app的react-scripts結(jié)合使用:Proxying API Requests in Development旺订,react-scripts應該是基于HTTP_PROXY環(huán)境變量做了一些封裝耸峭。
閱讀完本文淋纲,你將有一以下收獲:
- 如何更優(yōu)雅地為前端項目配置代理Proxy服務器
- 復現(xiàn)之前啃《HTTP權(quán)威指南》代理相關的知識
- 對easy-mock的使用限制有了新的認識
- 對process.env可以直接在React層展示感到震驚
- 了解到對process.env可以進行擴展的dotenv和expand-env兩個庫
主要分為3部分:
- 開發(fā)過程中的Proxy API 請求設置
- 手動配置proxy
- 環(huán)境變量式配置Proxy
開發(fā)過程中的Proxy API 請求設置
注意:這個特性可以在react-scripts@2.3以及更高版本中使用。
人們通常從將服務于后端實現(xiàn)的host和port业汰,同樣也為前端react應用提供服務样漆。
例如放祟,在一個應用部署后呻右,生產(chǎn)配置類似下面這樣:
/ -靜態(tài)服務器返回React應用和index.html
/todos -靜態(tài)服務器返回React應用和index.html
/api/todos -服務器會使用后端實現(xiàn)去處理所有/api/*的請求
但其實這樣的設置不是必須的声滥。然而,如果你確實有一個這樣的設置纽疟,在不考慮重定向它們到其他的host和port開發(fā)環(huán)境下污朽,那么寫出像fetch('/api/todos')這樣的請求時正常的膘壶。
為了告訴開發(fā)環(huán)境的服務器去代理任何開發(fā)環(huán)境中未知的請求到我們自己的api服務器洲愤,添加一個proxy到package.json的字段柬赐,例如:
"proxy":"http://localhost:4000"
使用這種形式的話官紫,當你在開發(fā)環(huán)境中使用fecth('api/todos')的時候,開發(fā)環(huán)境的服務器將識別出這不是一個靜態(tài)資源酝陈,然后將代理轉(zhuǎn)發(fā)你的請求到http://localhost:4000/api/todos 作為一個回調(diào)沉帮。生產(chǎn)環(huán)境服務器只能代理沒有text/html在Accept頭中的請求穆壕。
方便的是,這就避免了CORS問題以及類似像下面這樣的錯誤信息缨该。
Fetch API cannot load http://localhost:4000/api/todos. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
要知道proxy只有在開發(fā)環(huán)境中會有副作用贰拿,而且類似/api/todos 這樣的URL在生產(chǎn)環(huán)境中是否指向正確取決于我們熄云。你不需要使用/api前綴皱碘。任何沒有text/html請求頭的未識別的請求將會被代理到配置的服務器。
proxy選項支持HTTP健蕊,HTTPS以及WebSocket連接缩功。
如果proxy選項還不夠靈活的話都办,你可以去做自定義:
- 自己配置代理(未實驗)
- 服務器端開啟CORS(親測琳钉,express和koa均可實現(xiàn),koa可以直接使用koa-cors)
- 使用環(huán)境變量注入正確的服務器以及端口到應用(未實驗)
工科男的執(zhí)著:)
為了更好的說明問題啦桌,我們來做一次本地實驗:
- 啟動服務
npx creat-react-app my-app
cd my-app
npm run start
- 引入axios并發(fā)送請求
npm i axios --save
componentDidMount(){
axios.get('/foo')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
請求發(fā)送:"http://localhost:3000/foo"
錯誤信息:404
我們?yōu)閜ackage.json新增proxy服務器:
"proxy":"http://0.0.0.89:7300"
ctrl + s 熱更新react代碼后甫男,沒有生效板驳,依舊報404的錯誤若治。
npm run start 重啟本地服務后,代理服務器生效直砂,返回正常的數(shù)據(jù)静暂。
實現(xiàn)了自動將"http://localhost:3000" 請求轉(zhuǎn)發(fā)到"http://0.0.0.89:7300" 的服務器洽蛀。
不知道聰明的你們發(fā)現(xiàn)沒有,我們并沒有遇到CORS問題峡碉,因為在瀏覽器眼里鲫寄,我們還是將請求發(fā)送到"http://localhost:3000" 中的地来,它并不知道creat-react-app已經(jīng)將請求轉(zhuǎn)發(fā)到了"http://0.0.0.89:7300" 這個所謂的會觸發(fā)瀏覽器CORS安全策略的其他Origin未斑。
天真的瀏覽器:
請求發(fā)送路徑:
"http://localhost:3000" →"http://0.0.0.89:7300/foo"
響應返回路徑:
"http://0.0.0.89:7300/foo" →"http://localhost:3000"
備注:
1.此處需要重新運行npm run start 重啟本地服務蜡秽,否則在package.json中設置的proxy不會被檢測到并生效缆镣。
2.此處的服務器可以是公司內(nèi)網(wǎng)某臺虛擬機上的啟動的node服務,也可以是easy-mock等mock服務器(僅支持公司內(nèi)網(wǎng)部署版诉瓦,大搜車公網(wǎng)線上服務器不支持)。
因此我們得出一個結(jié)論:
creat-react-app腳手架可以結(jié)合package.json中的proxy實現(xiàn)請求轉(zhuǎn)發(fā)固额。
實驗成功斗躏!
手動配置proxy
注意:這個特性可以在react-scripts@1.0.0以及更高版本中使用。
如果proxy的默認配置不夠靈活笛臣,可以在package.json自定義一個像下面這樣形式的對象。
你也可以http-proxy-middleware或者http-proxy去實現(xiàn)静陈。
{
“proxy”:{
"/api":{
"target":"<url>",
"ws":true
}
}
}
所有與這個路徑相互匹配的請求將被代理轉(zhuǎn)發(fā)鲸拥。這包括了text/html類型的請求刑赶,這種類型是標準proxy選項不支持的懂衩。
如果你需要配置多個代理浊洞,你需要在定義幾個入口沛申。匹配規(guī)則還是那樣,這樣你才能使用正則匹配多個路徑尖淘。
{
// ...
"proxy": {
// Matches any request starting with /api
"/api": {
"target": "<url_1>",
"ws": true
// ...
},
// Matches any request starting with /foo
"/foo": {
"target": "<url_2>",
"ssl": true,
"pathRewrite": {
"^/foo": "/foo/beta"
}
// ...
},
// Matches /bar/abc.html but not /bar/sub/def.html
"/bar/[^/]*[.]html": {
"target": "<url_3>",
// ...
},
// Matches /baz/abc.html and /baz/sub/def.html
"/baz/.*/.*[.]html": {
"target": "<url_4>"
// ...
}
}
// ...
}
工科男的執(zhí)著村生,繼續(xù)來做一個實驗:
依然使用上面的my-app項目趁桃,proxy配置如下:
"proxy":{
"/api": {
"target": "http://0.0.0.89:7300",
"ws": true
},
"/foo": {
"target": "http://0.0.11.22:8848",
"ws": true,
"pathRewrite": {
"^/foo": "/foo/beta"
}
}
}
代碼如下:
axios.get('/api')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.get('/foo')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
執(zhí)行結(jié)果:
api接口和之前一致肄鸽,我們這里主要看重定向的foo接口典徘。
請求發(fā)送路徑:
"http://localhost:3000" →"http://0.0.11.22:8848/foo" →"http://0.0.11.22:8848/foo/beta"
響應返回路徑:
"http://0.0.11.22:8848/foo/beta" →"http://localhost:3000"
可以配置對個代理逮诲,我們此處使用的是"http://0.0.0.89:7300" 和"http://0.0.11.22:8848" 這個兩臺代理服務器幽告,其中
"http://0.0.0.89:7300" 提供了api接口冗锁,"http://0.0.11.22:8848" 提供了foo接口冻河。而且我們可以在代理服務器上重定向接口芋绸。
因此我們得出一個結(jié)論:
creat-react-app腳手架可以結(jié)合package.json中的proxy担敌,可以配置對個代理,而且我們可以在代理服務器上重定向接口马昙。
實驗成功行楞!
環(huán)境變量式配置proxy
這個功能在react-scripts@0.2.3及更高本版中適用子房。
react的項目可以使用已經(jīng)聲明好的環(huán)境變量就轧,這些變量就像是在你的js文件中定義的本地變量一樣。默認情況下妒御,已經(jīng)有NODE_ENV默認環(huán)境變量乎莉,以及其他的以REACT_APP_為前綴的環(huán)境變量。
環(huán)境變量在構(gòu)建期間是被嵌入進去的哼鬓。因為Create React App提供了靜態(tài)的HTML/CSS/JS打包魄宏,不能在runtime時被讀取到宠互。為了在runtime期間讀取到環(huán)境變量椭坚,你需要還在HTML到服務器的內(nèi)存善茎,并且在運行時替換占位符垂涯,就像這里描述的這樣:Injecting Data from the Server into the Page。另外你可以在任何你更改他們的時間里重新構(gòu)建應用骄蝇。
你需要使用REACT_APP_創(chuàng)建通用的環(huán)境變量九火。除了NODE_ENV之外的任何其他的變量將被忽略岔激,這是為了避免exposing a private key on the machine that could have the same name虑鼎。運行期間炫彩,只要你修改了環(huán)境變量媒楼,就需要重啟開發(fā)服務器戚丸。
這些環(huán)境變量將被定義在process.env限府。例如,有一個名叫REACT_APP_SECRET_CODE的環(huán)境變量世澜,它可以通過process.env.REACT_APP_SECRET_CODE暴露在我們的javascript文件中寥裂。
我們這里同樣也有一個內(nèi)建的叫做NODE_ENV的環(huán)境變量。你可以通過process.env.NODE_ENV去讀取它麻养。當你運行npm start時鳖昌,NODE_ENV的值是development许昨,當你運行npm test時糕档,NODE_ENV的值是test,而且當你運行npm run build構(gòu)建生產(chǎn)環(huán)境的包的時候翼岁,它通常是production琅坡。你不能的手動覆蓋NODE_ENV榆俺。這樣可以預防開發(fā)者錯把開發(fā)環(huán)境的代碼部署到生產(chǎn)環(huán)境茴晋。
這些環(huán)境變量可以用于根據(jù)項目的部署位置或使用超出版本控制的敏感數(shù)據(jù)來有條件地顯示信息诺擅。
首先啡直,你需要一個已經(jīng)定義的環(huán)境變量烁涌。例如,你想在form表單中控制一個secret變量酒觅。
render(){
return (
<div>
<small>你的應用運行在<b>{process.env.NODE_ENV}</b>模式撮执。</small>
<form>
<input type="hidden" defaultValue={process.env.REACT_APP_SECRET_CODE} />
</form>
</div>
);
}
在構(gòu)建期間,process.env.REACT_APP_SECRET_CODE將會被環(huán)境變量中的當前值替代舷丹。謹記NODE_ENV是自動設置的變量抒钱。
當你在瀏覽器查看input時,它已經(jīng)被設置成了abcde(或者是空)。
上面的表單從環(huán)境變量中搜索一個名叫REACT_APP_SECRET_CODE的變量谋币。為了使用這個值仗扬,我們需要將其定義在環(huán)境中瑞信。使用兩種方式可以做到,一種是在shell中定義秤涩,一種是.env文件中。
可以通過NODE_ENV去對一些操作進行控制:
if(process.env.NODE_ENV !== 'production'){
analytics.disable();
}
當你使用npm run build編譯app時匀谣,將會使文件變得更小溶锭。
在HTML中引用環(huán)境變量:
注意:這個特性在react-scripts@0.9.0以及更高版本中使用垫毙。
你可以在public/index.html中獲取到以REACT_APP_為前綴的環(huán)境變量。例如:
<title>%REACT_APP_WEBSITE_NAME%</title>
注意事項:
- 除了內(nèi)建變量(NODE_ENV和PUBLIC_URL),變量名必須以REACT_APP_開頭才能正常工作消请。
- 構(gòu)建期間環(huán)境變量可以被注入進去蚜枢。如果你想在運行期間注入它們需频,采用這個方法:Generating Dynamic <meta> Tags on the Server
在shell中添加臨時的環(huán)境變量
對于不同的操作系統(tǒng)苞七,環(huán)境變量的設置是不同的乾蓬。但是更加需要注意的是撵渡,這是創(chuàng)建變量的方式僅僅是當前shell session窗口有效。
Linux和macOS(Bash)
REACT_APP_SECRECT_CODE=abcdef npm start
還有一種創(chuàng)建.env文件定義環(huán)境變量的方式。
.env文件將被檢如源代碼控制铜跑。
其他.env文件將怎么使用?
這個特性僅在react-scripts@1.0.0及更高中使用
使用dotenv可以將.env中的值注入到process.env中。例如:
require('dotenv').config()
在項目的根目錄定義一個.env文件并鍵入如下內(nèi)容:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
然后就可以在process.env中訪問到了:
const db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
.env: Default.
.env.local: Local overrides. 加載除了test之外的環(huán)境變量。
.env.development, .env.test, .env.production: 公用的環(huán)境變量驱入。
.env.development.local, .env.test.local, .env.production.local:本地的環(huán)境變量。
左邊的比右邊的優(yōu)先級高:
npm start: .env.development.local, .env.development, .env.local, .env
npm run build: .env.production.local, .env.production, .env.local, .env
npm test: .env.test.local, .env.test, .env (note .env.local is missing)
如何將系統(tǒng)環(huán)境變量擴展到我們項目下的.env文件使用:
使用dotenv-expand
REACT_APP_VERSION=$npm_package_version
# also works:
# REACT_APP_VERSION=${npm_package_version}
在.env文件內(nèi)部也可以使用變量:
DOMAIN=www.example.com
REACT_APP_FOO=$DOMAIN/foo
REACT_APP_BAR=$DOMAIN/bar
工科男的執(zhí)著:)
簡單做個實驗:
touch .env
code .env
鍵入:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
代碼:
console.log("process.env.DB_HOST-->%s,process.env.DB_USER-->%s,process.env.DB_PASS-->%s",process.env.DB_HOST,process.env.DB_USER,process.env.DB_PASS)
實驗結(jié)果:
process.env.DB_HOST-->undefined,process.env.DB_USER-->undefined,process.env.DB_PASS-->undefined
實驗結(jié)果并不總是令人滿意巡通,問題在于不知道在何處require('dotenv').config(),可能需要在node層引入签孔,也可能需要借助webpack之類的工具,使得view層能訪問到救崔。
實驗失敗。
做一下總結(jié):
- 開發(fā)過程中的Proxy API 請求設置(默認選型,滿足大多數(shù)情況下需求)
- 手動配置Proxy (可實現(xiàn)多代理,重定向)
- 環(huán)境變量式配置Proxy (臨時變量方式簡單易用孕索,.env方式較為復雜,可以使用配置文件代替)
努力成為優(yōu)秀的前端工程師杭抠!
期待和大家交流,共同進步,歡迎大家加入我創(chuàng)建的與前端開發(fā)密切相關的技術(shù)討論小組:
- SegmentFault技術(shù)圈:ES新規(guī)范語法糖
- SegmentFault專欄:趁你還年輕,做個優(yōu)秀的前端工程師
- 知乎專欄:趁你還年輕,做個優(yōu)秀的前端工程師
- Github博客: 趁你還年輕233的個人博客
- 前端開發(fā)QQ群:660634678
微信公眾號: 人獸鬼 / excellent_developers
努力成為優(yōu)秀前端工程師!