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

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

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

默認值爲null,類型爲url,一個爲了發送http請求的代理。若是HTTP__PROXY或者http_proxy環境變量已經設置好了,那麼proxy設置將被底層的請求庫實現。

這個proxy字段目前我只瞭解到能夠與create-react-app的react-scripts結合使用:Proxying API Requests in Development,react-scripts應該是基於HTTP_PROXY環境變量作了一些封裝。html

閱讀完本文,你將有一如下收穫:前端

  • 如何更優雅地爲前端項目配置代理Proxy服務器
  • 復現以前啃《HTTP權威指南》代理相關的知識
  • 對easy-mock的使用限制有了新的認識
  • 對process.env能夠直接在React層展現感到震驚
  • 瞭解到對process.env能夠進行擴展的dotenv和expand-env兩個庫

主要分爲3部分:vue

  • 開發過程當中的Proxy API 請求設置
  • 手動配置proxy
  • 環境變量式配置Proxy

開發過程當中的Proxy API 請求設置

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

人們一般從將服務於後端實現的host和port,一樣也爲前端react應用提供服務。
例如,在一個應用部署後,生產配置相似下面這樣:java

/                   -靜態服務器返回React應用和index.html
/todos         -靜態服務器返回React應用和index.html
/api/todos   -服務器會使用後端實現去處理全部/api/*的請求

但其實這樣的設置不是必須的。然而,若是你確實有一個這樣的設置,在不考慮重定向它們到其餘的host和port開發環境下,那麼寫出像fetch('/api/todos')這樣的請求時正常的。node

爲了告訴開發環境的服務器去代理任何開發環境中未知的請求到咱們本身的api服務器,添加一個proxy到package.json的字段,例如:react

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

使用這種形式的話,當你在開發環境中使用fecth('api/todos')的時候,開發環境的服務器將識別出這不是一個靜態資源,而後將代理轉發你的請求到http://localhost:4000/api/todos 做爲一個回調。生產環境服務器只能代理沒有text/html在Accept頭中的請求。webpack

方便的是,這就避免了CORS問題以及相似像下面這樣的錯誤信息。ios

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只有在開發環境中會有反作用,並且相似/api/todos 這樣的URL在生產環境中是否指向正確取決於咱們。你不須要使用/api前綴。任何沒有text/html請求頭的未識別的請求將會被代理到配置的服務器。git

proxy選項支持HTTP,HTTPS以及WebSocket鏈接。
若是proxy選項還不夠靈活的話,你能夠去作自定義:

工科男的執着:)
爲了更好的說明問題,咱們來作一次本地實驗:

  • 啓動服務
npx creat-react-app my-app
cd my-app
npm run start
  • 引入axios併發送請求
npm i axios --save
componentDidMount(){
    axios.get('/foo')
        .then(function (response) {
          console.log(response);
        })
        .catch(function (error) {
          console.log(error);
        });
}

請求發送:"http://localhost:3000/foo"
錯誤信息:404

咱們爲package.json新增proxy服務器:

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

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

npm run start 重啓本地服務後,代理服務器生效,返回正常的數據。

實現了自動將"http://localhost:3000" 請求轉發到"http://0.0.0.89:7300" 的服務器。

不知道聰明的大家發現沒有,咱們並無遇到CORS問題,由於在瀏覽器眼裏,咱們仍是將請求發送到"http://localhost:3000" 中的,它並不知道creat-react-app已經將請求轉發到了"http://0.0.0.89:7300" 這個所謂的會觸發瀏覽器CORS安全策略的其餘Origin。

天真的瀏覽器:

clipboard.png

請求發送路徑:
"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.此處的服務器能夠是公司內網某臺虛擬機上的啓動的node服務,也能夠是easy-mock等mock服務器(僅支持公司內網部署版,大搜車公網線上服務器不支持)。

所以咱們得出一個結論:

creat-react-app腳手架能夠結合package.json中的proxy實現請求轉發。

實驗成功!

手動配置proxy

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

若是proxy的默認配置不夠靈活,能夠在package.json自定義一個像下面這樣形式的對象。
你也能夠http-proxy-middleware或者http-proxy去實現。

{
    「proxy」:{
        "/api":{
            "target":"<url>",
            "ws":true 
        }
    }
}

全部與這個路徑相互匹配的請求將被代理轉發。這包括了text/html類型的請求,這種類型是標準proxy選項不支持的。

若是你須要配置多個代理,你須要在定義幾個入口。匹配規則仍是那樣,這樣你才能使用正則匹配多個路徑。

{
  // ...
  "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>"
      // ...
    }
  }
  // ...
}

工科男的執着,繼續來作一個實驗:

依然使用上面的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);
    });

執行結果:
api接口和以前一致,咱們這裏主要看重定向的foo接口。

請求發送路徑:
"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接口。並且咱們能夠在代理服務器上重定向接口。

所以咱們得出一個結論:

creat-react-app腳手架能夠結合package.json中的proxy,能夠配置對個代理,並且咱們能夠在代理服務器上重定向接口。

實驗成功!

環境變量式配置proxy

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

react的項目可使用已經聲明好的環境變量,這些變量就像是在你的js文件中定義的本地變量同樣。默認狀況下,已經有NODE_ENV默認環境變量,以及其餘的以REACT_APP_爲前綴的環境變量。

環境變量在構建期間是被嵌入進去的。由於Create React App提供了靜態的HTML/CSS/JS打包,不能在runtime時被讀取到。爲了在runtime期間讀取到環境變量,你須要還在HTML到服務器的內存,而且在運行時替換佔位符,就像這裏描述的這樣:Injecting Data from the Server into the Page。另外你能夠在任何你更改他們的時間裏從新構建應用。

你須要使用REACT_APP_建立通用的環境變量。除了NODE_ENV以外的任何其餘的變量將被忽略,這是爲了不 exposing a private key on the machine that could have the same name。運行期間,只要你修改了環境變量,就須要重啓開發服務器。

這些環境變量將被定義在process.env。例如,有一個名叫REACT_APP_SECRET_CODE的環境變量,它能夠經過process.env.REACT_APP_SECRET_CODE暴露在咱們的javascript文件中。

咱們這裏一樣也有一個內建的叫作NODE_ENV的環境變量。你能夠經過process.env.NODE_ENV去讀取它。當你運行npm start時,NODE_ENV的值是development,當你運行npm test時,NODE_ENV的值是test,並且當你運行npm run build構建生產環境的包的時候,它一般是production。你不能的手動覆蓋NODE_ENV。這樣能夠預防開發者錯把開發環境的代碼部署到生產環境。

這些環境變量能夠用於根據項目的部署位置或使用超出版本控制的敏感數據來有條件地顯示信息。

首先,你須要一個已經定義的環境變量。例如,你想在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>
    );
}

在構建期間,process.env.REACT_APP_SECRET_CODE將會被環境變量中的當前值替代。謹記NODE_ENV是自動設置的變量。

當你在瀏覽器查看input時,它已經被設置成了abcde(或者是空)。
上面的表單從環境變量中搜索一個名叫REACT_APP_SECRET_CODE的變量。爲了使用這個值,咱們須要將其定義在環境中。使用兩種方式能夠作到,一種是在shell中定義,一種是.env文件中。

能夠經過NODE_ENV去對一些操做進行控制:

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

當你使用npm run build編譯app時,將會使文件變得更小。

在HTML中引用環境變量:

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

你能夠在public/index.html中獲取到以REACT_APP_爲前綴的環境變量。例如:
<title>%REACT_APP_WEBSITE_NAME%</title>

注意事項:

  • 除了內建變量(NODE_ENV和PUBLIC_URL),變量名必須以REACT_APP_開頭才能正常工做。
  • 構建期間環境變量能夠被注入進去。若是你想在運行期間注入它們,採用這個方法:Generating Dynamic <meta> Tags on the Server

在shell中添加臨時的環境變量
對於不一樣的操做系統,環境變量的設置是不一樣的。可是更加須要注意的是,這是建立變量的方式僅僅是當前shell session窗口有效。

Linux和macOS(Bash)

REACT_APP_SECRECT_CODE=abcdef npm start

還有一種建立.env文件定義環境變量的方式。

.env文件將被檢如源代碼控制。

其餘.env文件將怎麼使用?

這個特性僅在react-scripts@1.0.0及更高中使用
使用dotenv能夠將.env中的值注入到process.env中。例如:
require('dotenv').config()

在項目的根目錄定義一個.env文件並鍵入以下內容

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以外的環境變量。
.env.development, .env.test, .env.production: 公用的環境變量。
.env.development.local, .env.test.local, .env.production.local:本地的環境變量。

左邊的比右邊的優先級高:

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)

如何將系統環境變量擴展到咱們項目下的.env文件使用:

使用dotenv-expand

REACT_APP_VERSION=$npm_package_version
# also works:
# REACT_APP_VERSION=${npm_package_version}

在.env文件內部也可使用變量:

DOMAIN=www.example.com
REACT_APP_FOO=$DOMAIN/foo
REACT_APP_BAR=$DOMAIN/bar

工科男的執着:)
簡單作個實驗:

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)

實驗結果:

process.env.DB_HOST-->undefined,process.env.DB_USER-->undefined,process.env.DB_PASS-->undefined

實驗結果並不老是使人滿意,問題在於不知道在何處require('dotenv').config(),可能須要在node層引入,也可能須要藉助webpack之類的工具,使得view層能訪問到。

實驗失敗。
作一下總結:

  • 開發過程當中的Proxy API 請求設置(默認選型,知足大多數狀況下需求)
  • 手動配置Proxy (可實現多代理,重定向)
  • 環境變量式配置Proxy (臨時變量方式簡單易用,.env方式較爲複雜,可使用配置文件代替)

努力成爲優秀的前端工程師!

相關文章
相關標籤/搜索