如何理解package.json中的proxy字段像棘?

入職新公司以來缕题,第一個月接手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選項還不夠靈活的話都办,你可以去做自定義:

工科男的執(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未斑。

天真的瀏覽器:


image

請求發(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ù)討論小組:

努力成為優(yōu)秀前端工程師!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躺同,一起剝皮案震驚了整個濱河市椭员,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖咧栗,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件素征,死亡現(xiàn)場離奇詭異端蛆,居然都是意外死亡晚凿,警方通過查閱死者的電腦和手機应役,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事匹颤。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵堕汞,是天一觀的道長人灼。 經(jīng)常有香客問我灸芳,道長谒获,這世上最難降的妖魔是什么精耐? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任僵芹,我火速辦了婚禮,結(jié)果婚禮上茧彤,老公的妹妹穿的比我還像新娘珠洗。我一直安慰自己蛔糯,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布哼丈。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逃默。 梳的紋絲不亂的頭發(fā)上关噪,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天泽艘,我揣著相機與錄音,去河邊找鬼雳攘。 笑死无畔,一個胖子當著我的面吹牛闸昨,可吹牛的內(nèi)容都是我干的横辆。 我是一名探鬼主播划纽,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼醋奠!你這毒婦竟也來了例证?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤尿扯,失蹤者是張志新(化名)和其女友劉穎吝秕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棍现,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡娄柳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕉朵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅闽。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厦瓢,到底是詐尸還是另有隱情提揍,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布煮仇,位于F島的核電站劳跃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浙垫。R本人自食惡果不足惜售碳,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贸人,春花似錦、人聲如沸佃声。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽圾亏。三九已至十拣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間志鹃,已是汗流浹背夭问。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曹铃,地道東北人缰趋。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像陕见,于是被迫代替她去往敵國和親秘血。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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