【全栈React】第27天: 部署介绍

前端之家收集整理的这篇文章主要介绍了【全栈React】第27天: 部署介绍前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本文转载自:众成翻译
译者:iOSDevLog
链接http://www.zcfy.cc/article/3803
原文:https://www.fullstackreact.com/30-days-of-react/day-27/

今天,我们将探讨部署我们的应用所涉及的不同部分,以便外界可以使用我们的应用。

我们的应用通过这一点进行了测试,现在是时候让它起来为世界而活。本课程的其余部分将致力于将我们的应用部署到生产中。

@H_502_11@生产部署

在谈到部署时,我们有很多不同的选择:

  • 主机

  • 部署环境配置

  • 持续集成 (简称 CI)

  • 成本周期、网络带宽成本

  • 包大小

  • 更多

我们将看看不同的托管选项,明天看看部署我们的react应用的一些不同的方法,我们部署我们的应用。今天,我们将专注于让我们的应用准备好部署。

@H_502_11@弹出 (从create-react-app)

首先,我们需要在 web 应用中处理一些自定义,所以我们需要在目录的根中运行 npm run eject 命令。这是一个永久性的动作,现在这只是意味着我们将负责处理我们的应用结构的自定义 (没有我们的方便create-react-app的帮助)。

这是我 总是 说,做一个备份副本的应用。我们不能从 ejecting返回,但我们可以恢复到旧代码

我们可以通过运行由create-react-app结构生成器提供的弹出命令来 _弹出_:

npm run eject

ejectingcreate-react-app结构中,我们将看到我们的应用根目录中有很多新文件config/scripts/ 目录。npm run eject 命令创建了它在内部使用的所有文件,并在我们的应用中为我们编写了所有的文档。

create-react-app`生成器的关键方法称为webpack,它是一个模块打包器/生成器。

@H_502_11@webpack 基础知识

Webpack 是一个大社区的用户模块打包器,成吨的插件正在积极开发,有一个聪明的插件系统,是令人难以置信的快速,支持代码重装,和更多的多。

虽然我们没有真正调用它之前,我们一直在使用 webpack 这整个时间 (在npm start 的幌子下)。如果没有 webpack,我们就不可能只写import,并期望我们的代码加载。它的工作原理像这样,因为 webpack "看到"import 的关键字,并且知道我们需要在应用运行时可以访问路径上的代码

Webpack 为我们照顾热加载,几乎自动,可以加载和打包许多类型的文件包,它可以以逻辑方式拆分代码,以便支持延迟加载和收缩用户的初始下载大小。

这对我们是有意义的,因为我们的应用越来越大,更复杂,重要的是要知道如何操纵我们的构建工具。

例如,当我们要部署到不同的环境..。首先,对 webpack 的一个微小的介绍,它是什么以及它是如何工作的。

bundle.js做什么

当我们运行 npm start 查看生成文件之前我们弹出的应用,我们可以看到它为浏览器服务两个或更多的文件。第一个是index.htmlbundle.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) 相同的模块化处理。这种模块化范式释放了大量的力量。在本章的其余部分,我们将讨论这一权力的各个方面。

@H_502_11@复杂,对不对?

如果你不明白这一点没关系建立和维护 webpack 是一个复杂的项目,有大量的移动部件,它往往需要即使是最有经验的开发商而 "得到"。

我们将遍历我们将使用我们的 webpack 配置的不同部分,。如果它感觉压倒性,只是坚持我们的基础上,其余的将遵循。

随着我们对 Webpack 内部运作的新认识,让我们把注意力转向我们的应用。我们将对我们的 webpack 构建工具进行一些修改,以支持多种环境配置。

@H_502_11@环境配置

当我们准备好部署一个新的应用时,我们必须考虑一些我们在开发应用时不必关注的事情。

例如,假设我们正在请求 api 服务器的数据...... 在开发此应用时,我们可能会在本地计算机上运行 API 服务器的开发实例 (可通过localhost访问)。

当我们部署应用时,我们希望从外部主机请求数据,很可能不在发送代码的位置上,所以localhost 只是不能做到。

我们能够处理配置管理的一种方法是使用 .env文件 。这些 .env 文件将包含不同的变量,为我们不同的条件,但仍然提供了我们处理配置的正常方式的一种方式,。

通常情况下,我们将在根目录中保留一个.env 文件,以包含一个 全局 配置,可以在每个基础上按条件将其重写。

让我们安装一个称为dotenvnpm 程序包,以帮助我们进行此配置设置,

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

基本上我们要做的是:

  1. 加载我们的默认.env文件

  2. 加载任何环境的.env文件

  3. 将这两个变量以及任何默认变量(如NODE_ENV)合并在一起

  4. 我们将创建一个包含所有环境变量的新对象,并对每个值进行清理。

  5. 更新现有环境创建者的初始对象。

  6. @H_248_404@

    让我们忙吧 为了加载.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,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
    @H_502_11@每个配置环境自定义中间件

    我们在应用中使用了自定义日志再现中间件。这对于在我们的开发站点上工作是非常棒的,但是我们并不希望它在生产环境中处于活动状态。

    让我们更新我们的中间件配置,在开发时只使用日志中间件,而不是在所有环境中。在我们的项目的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,当我们运行应用的开发,我们将有loggingMiddleware 设置,而在任何其他环境中,我们已经禁用它。

    今天是一个漫长的,但明天是一个激动人心的一天,因为我们将得到应用和运行在远程服务器上。

    今天的工作很棒,明天见!

猜你在找的React相关文章