本文轉載自:衆成翻譯
譯者:iOSDevLog
連接:http://www.zcfy.cc/article/3803
原文:https://www.fullstackreact.com/30-days-of-react/day-27/css
今天,咱們將探討部署咱們的應用所涉及的不一樣部分,以便外界可使用咱們的應用。html
咱們的應用經過這一點進行了測試, 如今是時候讓它起來爲世界而活。本課程的其他部分將致力於將咱們的應用部署到生產中。react
在談到部署時, 咱們有不少不一樣的選擇:webpack
主機git
部署環境配置github
持續集成 (簡稱 CI)web
成本週期、網絡帶寬成本npm
包大小json
更多redux
咱們將看看不一樣的託管選項, 明天看看部署咱們的react應用的一些不一樣的方法, 咱們部署咱們的應用。今天, 咱們將專一於讓咱們的應用準備好部署。
create-react-app
)首先, 咱們須要在 web 應用中處理一些自定義, 因此咱們須要在目錄的根中運行 npm run eject
命令。這是一個永久性的動做,如今這只是意味着咱們將負責處理咱們的應用結構的自定義 (沒有咱們的方便create-react-app
的幫助)。
這是我 老是 說, 作一個備份副本的應用。咱們不能從
ejecting
返回, 但咱們能夠恢復到舊代碼。
咱們能夠經過運行由create-react-app
結構生成器提供的彈出命令來 _彈出_:
npm run eject
在 ejecting 的create-react-app
結構中, 咱們將看到咱們的應用根目錄中有不少新文件在config/
和 scripts/
目錄。npm run eject
命令建立了它在內部使用的全部文件, 並在咱們的應用中爲咱們編寫了全部的文檔。
create-react-app`生成器的關鍵方法稱爲webpack, 它是一個模塊打包器/生成器。
Webpack 是一個大社區的用戶模塊打包器, 成噸的插件正在積極開發, 有一個聰明的插件系統, 是使人難以置信的快速, 支持熱代碼重裝, 和更多的多。
雖然咱們沒有真正調用它以前, 咱們一直在使用 webpack 這整個時間 (在npm start
的幌子下)。若是沒有 webpack, 咱們就不可能只寫import
, 並指望咱們的代碼加載。它的工做原理像這樣, 由於 webpack "看到"import
的關鍵字, 而且知道咱們須要在應用運行時能夠訪問路徑上的代碼。
Webpack 爲咱們照顧熱加載, 幾乎自動, 能夠加載和打包許多類型的文件包, 它能夠以邏輯方式拆分代碼, 以便支持延遲加載和收縮用戶的初始下載大小。
這對咱們是有意義的, 由於咱們的應用愈來愈大, 更復雜, 重要的是要知道如何操縱咱們的構建工具。
例如, 當咱們要部署到不一樣的環境..。首先, 對 webpack 的一個微小的介紹, 它是什麼以及它是如何工做的。
bundle.js
作什麼當咱們運行 npm start
查看生成的文件以前咱們彈出的應用,咱們能夠看到它爲瀏覽器服務兩個或更多的文件。第一個是index.html
和bundle.js
.。webpack 服務器負責將bundle.js
.插入index.html
, 即便咱們不在index.html
文件中加載咱們的應用。
bundle.js
文件是一個巨大的文件, 包含咱們的應用須要運行的 全部 的 JavaScript 代碼, 包括依賴和咱們本身的文件。Webpack 有它本身的方法包裝文件在一塊兒, 因此當看原始的源碼它看起來有點有趣。
Webpack 已經對全部包含的 JavaScript 進行了一些轉換。值得注意的是, 它使用Babel以 ES5-compatible 的格式轉換咱們的 ES6 代碼, 。
若是您查看 app.js
,的註釋頭, 它有一個數字 "254":
/* 254 */ /*!********************!*\ !*** ./src/app.js ***! \********************/
模塊自己封裝在一個相似以下的函數中:
function(module, exports, __webpack_require__) { // The chaotic `app.js` code here }
咱們的 web 應用的每一個模塊都用這個簽名封裝在一個函數裏面。Webpack 已經給咱們的每一個應用的模塊這個功能容器以及模塊 ID (在app.js
的狀況下, 254)。
可是這裏的 "模塊" 不限於 ES6 模塊。
Remember how we "imported" the makeRoutes()
function in app.js
, like this:請記住, 咱們是如何在app.js
"導入" makeRoutes()
函數的, 以下所示:
import makeRoutes from './routes'
這裏的變量聲明的makeRoutes
看起來像在混亂的app.js
Webpack 模塊:
var _logo = __webpack_require__(/*! ./src/routes.js */ 255);
他看起來很奇怪, 主要是由於 Webpack 爲調試目的提供的在線評論。刪除該註釋:
var _logo = __webpack_require__(255);
咱們有簡單的舊 ES5 代碼, 而不是import
語句。
如今, 在這個文件中搜索./src/routes.js
。
/* 255 */ /*!**********************!*\ !*** ./src/routes.js ***! \**********************/
請注意, 它的模塊 ID 是 "255", 相同的整數傳遞給上面的 __webpack_require__
。
Webpack 將 一切 視爲一個模塊, 包括像logo.svg
這樣的圖像資產。咱們能夠經過在logo.svg
模塊的混亂中挑選出一條路徑來了解發生了什麼。您的路徑可能不一樣, 但它看起來像這樣:
static/media/logo.5d5d9eef.svg
若是您打開一個新的瀏覽器標籤並插入這個地址 (您的地址將是不一樣的... 匹配爲您生成的文件 webpack 的名稱):
http://localhost:3000/static/media/logo.5d5d9eef.svg
你應該獲得的React Logo:
所以, Webpack 爲 logo.svg
建立了一個 Webpack 模塊, 它指的是 Webpack 開發服務器上的 svg 路徑。因爲這種模塊化範例, 它可以智能地編譯以下語句:
import makeRoutes from './routes'
進入這 ES5 聲明:
var _makeRoutes = __webpack_require__(255);
咱們的 CSS 資產呢?是的, 一切 是 Webpack 的一個模塊。搜索字符串./src/app.css
:
Webpack 的index.html
沒有包含任何對 CSS 的引用。這是由於 Webpack 是經過bundle.js
包括咱們的 CSS 在這裏。當咱們的應用加載時, 這個神祕的 Webpack 模塊函數將app.css
的內容轉儲到頁面上的style
標籤中。
所以, 咱們知道 _什麼 正在發生: Webpack 已經卷起每個能夠想象的 "模塊" 爲咱們的應用進入bundle.js
'。你可能會問: 爲何?
第一個動機是廣泛的 JavaScript 包。Webpack 已經將咱們全部的 ES6 模塊轉換爲本身定製的 ES5-兼容 模塊語法。正如咱們簡要介紹的, 它將咱們全部的 JavaScript 模塊封裝在特殊功能中。它提供了一個模塊 ID 系統, 使一個模塊可以引用另外一個。
Webpack 和其餘打包器同樣, 將咱們全部的 JavaScript 模塊整合到一個文件中。它 可能 將 JavaScript 模塊放在單獨的文件中, 可是這須要比create-react-app
提供更多的配置。
然而, Webpack 比其餘打包器更重視這個模塊範例。正如咱們所看到的, 它適用於圖像資產, CSS 和 npm 包 (如React和 ReactDOM) 相同的模塊化處理。這種模塊化範式釋放了大量的力量。在本章的其他部分, 咱們將討論這一權力的各個方面。
複雜, 對不對?
若是你不明白這一點不要緊創建和維護 webpack 是一個複雜的項目, 有大量的移動部件, 它每每須要即便是最有經驗的開發商而 "獲得"。
咱們將遍歷咱們將使用咱們的 webpack 配置的不一樣部分,。若是它感受壓倒性, 只是堅持咱們的基礎上, 其他的將遵循。
隨着咱們對 Webpack 內部運做的新認識, 讓咱們把注意力轉向咱們的應用。咱們將對咱們的 webpack 構建工具進行一些修改, 以支持多種環境配置。
當咱們準備好部署一個新的應用時, 咱們必須考慮一些咱們在開發應用時沒必要關注的事情。
例如, 假設咱們正在請求 api 服務器的數據...... 在開發此應用時, 咱們可能會在本地計算機上運行 API 服務器的開發實例 (可經過localhost
訪問)。
當咱們部署應用時, 咱們但願從外部主機請求數據, 極可能不在發送代碼的位置上, 因此localhost
只是不能作到。
咱們可以處理配置管理的一種方法是使用 .env
文件 。這些 .env
文件將包含不一樣的變量, 爲咱們不一樣的條件, 但仍然提供了咱們處理配置的正常方式的一種方式,。
一般狀況下, 咱們將在根目錄中保留一個.env
文件, 以包含一個 全局 配置, 能夠在每一個基礎上按條件將其重寫。
讓咱們安裝一個稱爲dotenv
的npm
程序包, 以幫助咱們進行此配置設置,
npm install --save-dev dotenv
dotenv 庫幫助咱們將環境變量加載到咱們的環境中的應用的 ENV
中。
添加
.env
到咱們的.gitignore
文件一般是一個好主意, 因此咱們不簽入這些設置。傳統上, 建立一個
.env
文件的示例版本是一個好主意,。例如, 對於咱們的應用, 咱們能夠建立一個名爲.env.example
的必須變量。稍後, 另外一個開發人員 (或咱們, 幾個月後) 可使用
.env.example
文件做爲.env
文件應該是什麼樣的模板。
這些.env
文件能夠包含變量, 就好像它們是 unix 樣式的變量同樣。讓咱們建立一個全局的變量APP_NAME
設置爲30days:
touch .env echo "APP_NAME=30days" > .env
讓咱們瀏覽到爆炸的config/
目錄, 在那裏咱們將看到爲咱們寫的咱們全部的構建工具。咱們不會查看全部這些文件, 可是爲了瞭解 什麼 的狀況, 咱們將開始查找config/webpack.config.dev.js
。
此文件顯示了用於構建咱們的應用的全部 webpack 配置。它包括裝載、插件、入口點等。對於咱們當前的任務, 要查找的行是在 plugins
列表中定義 DefinePlugin()
:
module.exports = { // ... plugins: [ // ... // Makes some environment variables available to // the JS code, for example: // if (process.env.NODE_ENV === 'development') { // ... // }. See `env.js` new webpack.DefinePlugin(env), // ... ] }
webpack.DefinePlugin
插件採用了一個帶有 "鍵" 和 "值" 的對象, 並在咱們的代碼中找到了咱們使用"鍵"的全部位置, 並將它替換爲值。
例如, 若是 env
對象看起來像:
{ '__NODE_ENV__': 'development' }
咱們能夠在咱們的源使用變量__NODE_ENV__
, 它將被替換爲 'development', 即:
class SomeComponent extends React.Component { render() { return ( <div>Hello from {__NODE_ENV__}</div> ) } }
render()
函數的結果會說 "Hello from development"。
要將咱們本身的變量添加到咱們的應用中, 咱們將使用這個env
對象, 並添加咱們本身的定義。向上滾動到文件頂部, 咱們將看到它當前是從 config/env.js
文件中建立和導出的。
看着 config/env.js
文件, 咱們能夠看到, 它將全部的變量都放在咱們環境, 並將NODE_ENV
添加到環境中, 以及任何以 REACT_APP_
爲前綴的變量。
// ... module.exports = Object .keys(process.env) .filter(key => REACT_APP.test(key)) .reduce((env, key) => { env['process.env.' + key] = JSON.stringify(process.env[key]); return env; }, { 'process.env.NODE_ENV': NODE_ENV });
咱們能夠跳過該操做的全部複雜部分, 由於咱們只須要修改第二個參數以減小函數, 換句話說, 咱們將更新對象:
{ 'process.env.NODE_ENV': NODE_ENV }
該對象是歸併函數的_初始_對象。
reduce
函數將全部以REACT_APP_
爲前綴的變量_合併_到此對象中,因此咱們老是在咱們的源代碼中替換process.env.NODE_ENV
。
基本上咱們要作的是:
加載咱們的默認.env
文件
加載任何環境的.env
文件
將這兩個變量以及任何默認變量(如NODE_ENV
)合併在一塊兒
咱們將建立一個包含全部環境變量的新對象,並對每一個值進行清理。
更新現有環境建立者的初始對象。
讓咱們忙吧 爲了加載.env
文件,咱們須要導入dotenv
包。 咱們還將從標準節點庫導入path
庫,併爲路徑設置一些變量。
Let's update the config/env.js
file咱們來更新config / env.js
文件
var REACT_APP = /^REACT_APP_/i; var NODE_ENV = process.env.NODE_ENV || 'development'; const path = require('path'), resolve = path.resolve, join = path.join; const currentDir = resolve(__dirname); const rootDir = join(currentDir, '..'); const dotenv = require('dotenv');
要加載全局環境,咱們將使用dotenv
庫公開的config()
函數,並傳遞根目錄中加載的.env
文件的路徑。 咱們還將使用相同的功能在config/
目錄中查找名稱爲NODE_ENV.config.env
.的文件。 此外,咱們不但願這些方法之一出錯,因此咱們將添加一個silent: true
的附加選項,以便若是找不到該文件,則不會拋出異常。
// 1\. Step one (loading the default .env file) const globalDotEnv = dotenv.config({ path: join(rootDir, '.env'), silent: true }); // 2\. Load the environment config const envDotEnv = dotenv.config({ path: join(currentDir, NODE_ENV + `.config.env`), silent: true });
接下來, 讓咱們將全部這些變量串聯在一塊兒, 並在這個對象中包括咱們的 NODE_ENV
選項。Object.assign()
方法建立一個 新 對象, 並從右向左合併每一個對象。這樣, 環境配置變量
const allVars = Object.assign({}, { 'NODE_ENV': NODE_ENV }, globalDotEnv, envDotEnv);
使用當前的設置, allVars
變量的外觀將以下所:
{ 'NODE_ENV': 'development', 'APP_NAME': '30days' }
最後, 讓咱們建立一個將這些變量放在 process.env
中的對象, 並確保它們是有效的字符串 (使用JSON.stringify
)。
const initialVariableObject = Object.keys(allVars) .reduce((memo, key) => { memo['process.env.' + key.toUpperCase()] = JSON.stringify(allVars[key]); return memo; }, {});
使用咱們當前的設置(在根目錄中有.env
文件),這將建立initialVariableObject
爲如下對象:
{ 'process.env.NODE_ENV': '"development"', 'process.env.APP_NAME': '"30days"' }
如今, 咱們可使用這個 initialVariableObject
做爲原始module.exports
的第二個參數。讓咱們更新它以使用這個對象:
module.exports = Object .keys(process.env) .filter(key => REACT_APP.test(key)) .reduce((env, key) => { env['process.env.' + key] = JSON.stringify(process.env[key]); return env; }, initialVariableObject);
如今, 咱們的代碼中的任何位置均可以使用咱們在 .env
文件中設置的變量。
因爲咱們正在向咱們的應用中的離線站點發出請求, 讓咱們使用咱們的新配置選項來更新此主機。
假設默認狀況下, 咱們但願將 TIME_SERVER 設置爲 http://localhost:3001
,這樣, 若是在環境配置中不設置TIME_SERVER
,它將默認爲本地主機。咱們能夠經過將TIME_SERVER
變量添加到全局 "。
讓咱們更新 .env
文件, 使其包括此時間服務器:
APP_NAME=30days TIME_SERVER='http://localhost:3001'
如今, 咱們已經開發的 "開發" 與服務器託管在 heroku。咱們能夠設置咱們的config/development.config.env
文件, 以設置 TIME_SERVER
變量, 它將覆蓋全局項:
TIME_SERVER='https://fullstacktime.herokuapp.com'
如今, 當咱們運行npm start
時, 任何出現的process.env.TIME_SERVER
將被替換爲優先值。
讓咱們更新咱們的src/redux/modules/currentTime.js
模塊來使用新的服務器, 而不是咱們之前使用的硬編碼的。
// ... export const reducer = (state = initialState, action) => { // ... } const host = process.env.TIME_SERVER; export const actions = { updateTime: ({timezone = 'pst', str='now'}) => ({ type: types.FETCH_NEW_TIME, meta: { type: 'api', url: host + '/' + timezone + '/' + str + '.json', method: 'GET' } }) }
如今, 對於咱們的生產部署, 咱們將使用 heroku 應用, 所以, 讓咱們在config/
下建立development.config.env
的一份拷貝爲production.config.env
。
cp config/development.config.env config/production.config.env
咱們在應用中使用了自定義日誌再現中間件。這對於在咱們的開發站點上工做是很是棒的, 可是咱們並不但願它在生產環境中處於活動狀態。
讓咱們更新咱們的中間件配置, 在開發時只使用日誌中間件, 而不是在全部環境中。在咱們的項目的src/redux/configureStore.js
文件中, 咱們用一個簡單的數組加載了咱們的中間件:
let middleware = [ loggingMiddleware, apiMiddleware ]; const store = createStore(reducer, applyMiddleware(...middleware));
如今, 咱們在咱們的文件中有了 process.env.NODE_ENV
, 咱們能夠更新middleware
數組, 這取決於咱們正在運行的環境。讓咱們更新它, 若是咱們在開發環境中只添加日誌記錄,:
let middleware = [apiMiddleware]; if ("development" === process.env.NODE_ENV) { middleware.unshift(loggingMiddleware); } const store = createStore(reducer, applyMiddleware(...middleware));
如今, 當咱們運行應用的開發, 咱們將有loggingMiddleware
設置, 而在任何其餘環境中, 咱們已經禁用它。
今天是一個漫長的, 但明天是一個激動人心的一天, 由於咱們將獲得應用和運行在遠程服務器上。
今天的工做很棒, 明天見!