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

入職新公司以來,第一個月接手vue項目,第二個月接手angularjs項目,第三個月加入react重構(gòu)項目。心生感嘆:業(yè)務(wù)驅(qū)動式學(xué)習(xí)是一種高效率的學(xué)習(xí)方式,保持好奇心,在業(yè)務(wù)中快速成長!
新項目中在package.json中有一個proxy字段,這是我從來沒接觸過的,因此就有了此文的誕生,我使用create-react-app 新建了一個最原始狀態(tài)的項目,對proxy字段與create-react-app之間的糾葛展開了學(xué)習(xí)。

在npm-configuration中,對proxy有如下解釋:

默認(rèn)值為null,類型為url,一個為了發(fā)送http請求的代理。如果HTTP__PROXY或者h(yuǎn)ttp_proxy環(huán)境變量已經(jīng)設(shè)置好了,那么proxy設(shè)置將被底層的請求庫實現(xiàn)。

這個proxy字段目前我只了解到可以與create-react-app的react-scripts結(jié)合使用:Proxying API Requests in Development,react-scripts應(yīng)該是基于HTTP_PROXY環(huán)境變量做了一些封裝。

閱讀完本文,你將有一以下收獲:

  • 如何更優(yōu)雅地為前端項目配置代理Proxy服務(wù)器
  • 復(fù)現(xiàn)之前啃《HTTP權(quán)威指南》代理相關(guān)的知識
  • 對easy-mock的使用限制有了新的認(rèn)識
  • 對process.env可以直接在React層展示感到震驚
  • 了解到對process.env可以進(jìn)行擴(kuò)展的dotenv和expand-env兩個庫

主要分為3部分:

  • 開發(fā)過程中的Proxy API 請求設(shè)置
  • 手動配置proxy
  • 環(huán)境變量式配置Proxy

開發(fā)過程中的Proxy API 請求設(shè)置

注意:這個特性可以在react-scripts@2.3以及更高版本中使用。

人們通常從將服務(wù)于后端實現(xiàn)的host和port,同樣也為前端react應(yīng)用提供服務(wù)。
例如,在一個應(yīng)用部署后,生產(chǎn)配置類似下面這樣:

/                   -靜態(tài)服務(wù)器返回React應(yīng)用和index.html
/todos         -靜態(tài)服務(wù)器返回React應(yīng)用和index.html
/api/todos   -服務(wù)器會使用后端實現(xiàn)去處理所有/api/*的請求

但其實這樣的設(shè)置不是必須的。然而,如果你確實有一個這樣的設(shè)置,在不考慮重定向它們到其他的host和port開發(fā)環(huán)境下,那么寫出像fetch('/api/todos')這樣的請求時正常的。

為了告訴開發(fā)環(huán)境的服務(wù)器去代理任何開發(fā)環(huán)境中未知的請求到我們自己的api服務(wù)器,添加一個proxy到package.json的字段,例如:

"proxy":"http://localhost:4000"

使用這種形式的話,當(dāng)你在開發(fā)環(huán)境中使用fecth('api/todos')的時候,開發(fā)環(huán)境的服務(wù)器將識別出這不是一個靜態(tài)資源,然后將代理轉(zhuǎn)發(fā)你的請求到http://localhost:4000/api/todos 作為一個回調(diào)。生產(chǎn)環(huán)境服務(wù)器只能代理沒有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請求頭的未識別的請求將會被代理到配置的服務(wù)器。

proxy選項支持HTTP,HTTPS以及WebSocket連接。
如果proxy選項還不夠靈活的話,你可以去做自定義:

工科男的執(zhí)著:)
為了更好的說明問題,我們來做一次本地實驗:

  • 啟動服務(wù)
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服務(wù)器:

"proxy":"http://0.0.0.89:7300"

ctrl + s 熱更新react代碼后,沒有生效,依舊報404的錯誤。

npm run start 重啟本地服務(wù)后,代理服務(wù)器生效,返回正常的數(shù)據(jù)。

實現(xiàn)了自動將"http://localhost:3000" 請求轉(zhuǎn)發(fā)到"http://0.0.0.89:7300" 的服務(wù)器。

不知道聰明的你們發(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"

響應(yīng)返回路徑:
"http://0.0.0.89:7300/foo" →"http://localhost:3000"
備注:
1.此處需要重新運(yùn)行npm run start 重啟本地服務(wù),否則在package.json中設(shè)置的proxy不會被檢測到并生效。
2.此處的服務(wù)器可以是公司內(nèi)網(wǎng)某臺虛擬機(jī)上的啟動的node服務(wù),也可以是easy-mock等mock服務(wù)器(僅支持公司內(nèi)網(wǎng)部署版,大搜車公網(wǎng)線上服務(wù)器不支持)。

因此我們得出一個結(jié)論:

creat-react-app腳手架可以結(jié)合package.json中的proxy實現(xiàn)請求轉(zhuǎn)發(fā)。

實驗成功!

手動配置proxy

注意:這個特性可以在react-scripts@1.0.0以及更高版本中使用。

如果proxy的默認(rèn)配置不夠靈活,可以在package.json自定義一個像下面這樣形式的對象。
你也可以http-proxy-middleware或者h(yuǎn)ttp-proxy去實現(xiàn)。

{
    “proxy”:{
        "/api":{
            "target":"<url>",
            "ws":true 
        }
    }
}

所有與這個路徑相互匹配的請求將被代理轉(zhuǎn)發(fā)。這包括了text/html類型的請求,這種類型是標(biāo)準(zhǔn)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"

響應(yīng)返回路徑:
"http://0.0.11.22:8848/foo/beta" →"http://localhost:3000"

可以配置對個代理,我們此處使用的是"http://0.0.0.89:7300" 和"http://0.0.11.22:8848" 這個兩臺代理服務(wù)器,其中
"http://0.0.0.89:7300" 提供了api接口,"http://0.0.11.22:8848" 提供了foo接口。而且我們可以在代理服務(wù)器上重定向接口。

因此我們得出一個結(jié)論:

creat-react-app腳手架可以結(jié)合package.json中的proxy,可以配置對個代理,而且我們可以在代理服務(wù)器上重定向接口。

實驗成功!

環(huán)境變量式配置proxy

這個功能在react-scripts@0.2.3及更高本版中適用。

react的項目可以使用已經(jīng)聲明好的環(huán)境變量,這些變量就像是在你的js文件中定義的本地變量一樣。默認(rèn)情況下,已經(jīng)有NODE_ENV默認(rèn)環(huán)境變量,以及其他的以REACT_APP_為前綴的環(huán)境變量。

環(huán)境變量在構(gòu)建期間是被嵌入進(jìn)去的。因為Create React App提供了靜態(tài)的HTML/CSS/JS打包,不能在runtime時被讀取到。為了在runtime期間讀取到環(huán)境變量,你需要還在HTML到服務(wù)器的內(nèi)存,并且在運(yùn)行時替換占位符,就像這里描述的這樣:Injecting Data from the Server into the Page。另外你可以在任何你更改他們的時間里重新構(gòu)建應(yīng)用。

你需要使用REACT_APP_創(chuàng)建通用的環(huán)境變量。除了NODE_ENV之外的任何其他的變量將被忽略,這是為了避免exposing a private key on the machine that could have the same name。運(yùn)行期間,只要你修改了環(huán)境變量,就需要重啟開發(fā)服務(wù)器。

這些環(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去讀取它。當(dāng)你運(yùn)行npm start時,NODE_ENV的值是development,當(dāng)你運(yùn)行npm test時,NODE_ENV的值是test,而且當(dāng)你運(yùn)行npm run build構(gòu)建生產(chǎn)環(huán)境的包的時候,它通常是production。你不能的手動覆蓋NODE_ENV。這樣可以預(yù)防開發(fā)者錯把開發(fā)環(huán)境的代碼部署到生產(chǎn)環(huán)境。

這些環(huán)境變量可以用于根據(jù)項目的部署位置或使用超出版本控制的敏感數(shù)據(jù)來有條件地顯示信息。

首先,你需要一個已經(jīng)定義的環(huán)境變量。例如,你想在form表單中控制一個secret變量。

render(){
    return (
        <div>
            <small>你的應(yīng)用運(yùn)行在<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)境變量中的當(dāng)前值替代。謹(jǐn)記NODE_ENV是自動設(shè)置的變量。

當(dāng)你在瀏覽器查看input時,它已經(jīng)被設(shè)置成了abcde(或者是空)。
上面的表單從環(huán)境變量中搜索一個名叫REACT_APP_SECRET_CODE的變量。為了使用這個值,我們需要將其定義在環(huán)境中。使用兩種方式可以做到,一種是在shell中定義,一種是.env文件中。

可以通過NODE_ENV去對一些操作進(jìn)行控制:

if(process.env.NODE_ENV !== 'production'){
   analytics.disable();
}

當(dāng)你使用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)境變量可以被注入進(jìn)去。如果你想在運(yùn)行期間注入它們,采用這個方法:Generating Dynamic <meta> Tags on the Server

在shell中添加臨時的環(huán)境變量
對于不同的操作系統(tǒng),環(huán)境變量的設(shè)置是不同的。但是更加需要注意的是,這是創(chuàng)建變量的方式僅僅是當(dā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)境變量擴(kuò)展到我們項目下的.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è)置(默認(rèn)選型,滿足大多數(shù)情況下需求)
  • 手動配置Proxy (可實現(xiàn)多代理,重定向)
  • 環(huán)境變量式配置Proxy (臨時變量方式簡單易用,.env方式較為復(fù)雜,可以使用配置文件代替)

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

期待和大家交流,共同進(jìn)步,歡迎大家加入我創(chuàng)建的與前端開發(fā)密切相關(guān)的技術(shù)討論小組:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容