本文转载自:众成翻译
译者: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
设置, 而在任何其余环境中, 咱们已经禁用它。
今天是一个漫长的, 但明天是一个激动人心的一天, 由于咱们将获得应用和运行在远程服务器上。
今天的工做很棒, 明天见!