【问题标题】:How do you manage repositories for production/deployment of Node-React app?您如何管理用于生产/部署 Node-React 应用程序的存储库?
【发布时间】:2020-03-12 03:31:59
【问题描述】:

不久前,我们曾经有服务器渲染页面,然后 React 出现在客户端渲染和单页面应用程序中。它引入了虚拟 DOM 并改变了我们编写代码的方式。

在编写代码之前,我们需要所有这些反应库并将它们作为依赖项安装。现在我们可以分解成许多组件,拥有许多 css 和 scss 文件,包括图像。但最后我们将构建文件,制作紧凑的捆绑包并从构建文件夹提供服务。

快速获取路线

app.get('*', (req,res) =>{
    res.sendFile(path.join(__dirname+'/client/build/index.html'));
});

这是我的理解:

Build 文件夹是 webpack 组合所有文件并创建准备部署的缩小包的地方。该文件基本上是每个浏览器都可以理解的简单 HTML 和 JS 文件。由于所有浏览器都不理解 ES6 等等,我们必须将所有这些文件转换为每个浏览器都能理解的普通语言。

另外,webpack-dev 服务器仅用于开发目的,我们不会将其运行到生产环境中。

  1. 虚拟 DOM/真实 DOM 是否仅用于开发目的?或者 在构建缩小文件时,这些反应库是否也被转堆?如果以后是这种情况,反应是在客户端浏览器的后台模式下运行的吗?我想知道在构建应用后,react 如何处理客户端路由。
  2. 如何管理 Node-React 应用程序的 github 存储库?您是否保留两个不同的存储库,一个用于前端,另一个用于后端?行业标准是什么?
  3. 如果你保留两个仓库,你如何部署前端代码?因为您无法将 webpack-dev-server 运行到生产环境中。您也不能在后端(快速服务器)中指定公共静态(构建文件夹),因为它们在两个存储库中分开。这两个存储库的集成是如何发生的(假设我们有两个 AWS EC2 实例,每个实例一个)或从前端存储库提供前端服务??)。你真的可以在生产中使用像 npm serve 这样的东西吗??

我想做什么?

我想在 AWS 上部署我的 node-react 应用程序。我在 github 上只有一个存储库。我的仓库中有一个“客户端”文件夹,所有反应代码都位于其 package.json 文件中。服务器的所有其他文件都在根文件夹内(服务器没有自己的文件夹,文件分散在根文件夹内)。所以有两个 package.json 文件,一个在服务器根文件夹内,一个在客户端文件夹内。我打算在 docker 容器上运行 my-node 应用程序。

请帮助我了解代码托管和部署的核心概念和标准实践,以了解大规模企业应用程序。

【问题讨论】:

  • 您正试图将“虚拟 DOM”概念和转成 ES5 兼容代码的常规 JS 文件混为一谈。两者是两种不同的东西。 VDOM 是 React 创造的一个概念,其代码在 ReactJS 库中
  • “我知道什么是真实 dom 和虚拟 dom 以及它们是如何工作的” - 你确定吗?听起来您认为虚拟 dom 仅用于开发?
  • 就像@Rikin 说的,虚拟dom 是由react 管理的。它与 webpack 或 babel 没有任何关系。你是对的,你使用 webpack 和 babel before 你的代码被执行来转译和捆绑它。代码被转译和捆绑后,就不再使用了。但它们永远不会对 VDOM 产生影响。
  • 好好想想你的 React 应用程序需要哪些东西。您在组件中导入 react,这意味着您需要 react 包中的一些 javascript 才能使您的代码正常工作。您永远不会导入 webpack 或 babel,它们是在您的代码之外使用的工具,用于操作代码本身。因此,当您捆绑项目时,react 代码也包含在该捆绑包中以及您使用的任何其他库中。
  • 需要 React 以及 React DOM 库(无论是否转译)并发送到客户端,以便在客户端浏览器上运行您的代码。

标签: node.js reactjs amazon-web-services docker continuous-deployment


【解决方案1】:

我不会在这里解释您问题中的所有要点,因为@Arnav Yagnik 和@PrivateOmega 在解释其中大部分方面都做得非常出色。我绝对建议您在阅读此答案之前正确阅读他们的答案并阅读提供的链接以获取更多信息

我想解决您部署 Node-React 应用程序的问题。通常,在生产中,前端(React)和后端(Node)都有不同的部署(或您在问题中提到的“存储库”)。这允许您的后端位于 EC2 实例中,例如,具有自动缩放功能,以确保它能够处理所有传入的请求。

正如前面的答案和你的问题中提到的,webpack 将 React 文件编译并缩小为简单的 HTML 和 JS 文件,大多数浏览器都可以运行(我不打算在这里解释 VirtualDOM,因为它已经在其他答案中得到了完美的解释)。然后,您将获取这些缩小的文件并从 S3 存储桶中提供它们,因为它再次是一个单页应用程序(也在其他答案中讨论过)并且业务逻辑已经在缩小的 JS 文件中,它只是简单地发送对后端服务器的所有请求。

现在对于前端,您可以使用 TravisCI 将 build 文件夹(您在问题中提到的那个)部署到 EC2 实例并提供服务使用 NGINX 或者如果您可以正确配置 CDN 部署,您可以从 S3 存储桶提供文件以获得最佳性能。

您可以将服务 React 应用程序想象为向用户的浏览器发送一个神秘的代码块。现在您可以部署这个神秘的代码块到一个公开可用的 S3 存储桶,并从那里提供它。同样,由于 webpack 和缩小/丑化,任何人都无法正确理解您的原始代码是什么,请记住,例如,您仍然可以访问 Chrome 的 Sources 选项卡中的所有代码。

【讨论】:

  • 我可以通过 CDN 进行部署,同时仍然管理用户会话(Cookie 或 JWT 令牌)吗?
  • 当然,Cookie 和 JWT 令牌以及其他详细信息都存储在浏览器中。在您的应用程序中,您将指示它在浏览器中存储这些详细信息。事实上,你可以自己在 Chrome 上查看,只需 Inspect 并转到 Application 选项卡,你会发现 Stackoverflow 在 Local、Session Storage 和 Cookies 中存储了相当多的信息。去玩吧。 :) 每个浏览器都有一个叫做会话存储和本地存储的东西,你可以在 React 中实现它,也可以阅读这里了解更多信息:robinwieruch.de/local-storage-react
【解决方案2】:

我想用不同的方法解决这个问题。

服务器渲染页面:概念没有改变,服务器在遇到 DOC 请求时必须以 html 响应。现在 HTML 可能包含也可能不包含脚本(可以是内联或外部服务器地址)。如果有问题的上下文,您仍然可以发送 HTML,它将下载您编写的脚本(可能包括或不包括反应)。在大多数情况下,您可以发送带有脚本标签的空 html,这些标签将通过网络下载脚本并执行它们,其中包含所有呈现逻辑。

回答您的问题: 第一:在单线程 JS 中没有后台模式(除非我们想讨论工人,但我们可以将它们排除在讨论之外)。通过编写代码,您不会与任何 DOM 交互。您正在指示您的组件(由 React 扩展)何时更改其状态以及何时重新渲染(setState)。 React 在内部计算虚拟 DOM 并与 Real DOM 进行比较以计算要在 Real DOM 上进行的实际更改(这是非常抽象的答案,要获得更多理解,请阅读反应文档,这里的基线是您不与任何 DOM 交互只是指示 React 核心库何时更新以及更新状态是什么)

第二个:如果你想支持 SSR(服务器渲染页面)。我建议使用不同的 包制作 2 个文件夹,client(这将包括所有客户端组件和逻辑)和 server(将包括所有服务器端逻辑)。 json 因为两个应用程序的包不同。这里没有这样的行业标准,你的船应该可以工作,但通常基于逻辑实体创建目录应该满足分离和可维护性,如果将来你认为你想分叉服务器和客户端在单独的 repos 中,它肯定会让这个过程变得简单。

3rd:你避免在生产环境中运行 webpack-dev-server。文件通常不会被混淆,因此有效负载很重(不要忘记您的书面代码就在那里)。即使您想制作不同的 repos,服务器也可以吐出 html,并且 html 可以向您的客户端服务器请求脚本。

如何部署:部署代码并运行:

节点服务器/app.js

在 app.js 中,您可以编写您提到的位置块。

附: :如果您只需要具有该位置块的服务器。你真的需要快递服务器吗?您可以将客户端构建上传到 CDN,并将您的域路由到 CDN 中的 index.html 服务(这里也可以使用 s3 存储桶)

【讨论】:

  • 感谢您的回复和时间。这仍然无法消除我现在的困惑!
【解决方案3】:

我想从尽可能多地清理术语开始。

就像你说的 server rendered pages 在过去是一个更突出的标准,但是随着 React 的引入,它根本没有改变,因为即使 React 也支持 Server rendering or SSR,这意味着 HTML 页面是在服务器端,然后使用浏览器提供给客户端。

client side rendering 的意思是,一个 HTML 页面被加载到浏览器,然后 javascript 代码在这些 HTML 页面上呈现内容并使其具有交互性。

single page application 的概念是我们只有一个 HTML 文件或基础 HTML 页面,基于用户交互和来自服务器的数据在其之上不断重写。

Virtual Dom 是 React 引入的一个惊人的概念。 React 库代码以树的形式在内存中重新创建 HTML 页面的所有元素(称为 DOM 元素)的结构。这使得名为 Fiber 的 React 算法能够根据路由更新或任何其他更改首先在此树状结构上协调适当的更改,然后再将它们转换为 HTML 页面中的实际元素。

Babel 是一个转译器,用于将浏览器引擎尚未开始支持的最新功能转译为他们可以理解的代码,通常是 ES6+ 代码到 ES6 之前的代码,因为所有浏览器都支持。在 React 应用程序中,如果你使用 JSX 语法编写应用程序,babel 也支持将 JSX 转换为普通的 javascript。

是的,由于 React 组件的组合特性,可以将页面分解为多个组件,这意味着我们可以通过组合小的和更集中的东西来构建复杂的东西。

在最终将其提供给最终用户之前,我们不能因为代码量大而导致 Web 应用程序滞后,因此在构建过程中,诸如缩小(删除空格等)和其他优化之类的事情,例如组合多个 javascript文件到一个等完成,然后像你说的那样从构建文件夹提供压缩包。

是的,构建文件夹是 webpack 进行缩小和组合以创建尽可能小的包的地方。它是每个浏览器都可以理解的基本 HTML 和 JS 文件,如果代码包含特定浏览器不支持的内容,则相应的支持代码或称为 polyfill 的内容也会与它捆绑在一起。从技术上讲,你不能说浏览器只理解 ES6 之前的代码,因为很多浏览器引擎已经实现了很多 ES6 特性。

Webpack 开发服务器仅用于通过端口(如 node.js 服务器)为 webpack 应用程序提供服务,并为我们提供实时重新加载等功能,当您不断更改应用程序代码库时需要此功能,而在生产,因为就像我们之前所说的,在生产时它只是 HTML 和 JS,没有人对这些文件进行任何更改。

  1. Virtual DOM 是 React Code 使用的内存表示或概念,就像我们有堆栈和队列一样,它不仅在开发时使用。是和否。因为我认为运行应用程序所需的 React 源代码的适当部分也将在生成生产包之前进行捆绑。

  2. 我会说,不要有预设的方式,因为这完全取决于开发人员和团队,因为我看到人们使用 2 个单独的 repos,因为前端人员在前端工作,而后端人员从事后端工作。但是也有一种情况,每个人都是全栈开发人员,从技术上讲,您可以将它放在带有单个 package.json 的单个 repo 中,并使用后端来提供前端文件,您必须手动安装每个 react 依赖项,并且不能直接使用 CRA 或像生成器一样创建反应应用程序。

  3. 2 个存储库与生产中的前端部署有什么关系?您不需要在生产环境中运行 webpack-dev-server 来服务器文件。您可以创建一个生产包,然后设置任何 http 服务器来为生成的包提供服务。

关于您当前的情况,我想说的是,您可以使用单个 package.json 并将所有依赖项一起安装,或者使用类似 lernayarn workspaces 的东西使用 monorepo 方法,而不是使用 2 个 package.json。

但对于初学者来说,我建议使用 2 个独立的存储库来减少遇到的问题。

如果你不知道,你可以在 ES6 之前的代码中编写 React,也可以不使用 JSX。

【讨论】:

  • 如果您将文件保存在两个存储库中,并且您没有使用 express 或任何 http 服务器来提供构建文件,那么,您将如何提供前端代码?只是好奇。如果它们在两个存储库中,如何进行集成?我的理解是我们需要在快速服务器中指定静态文件夹,这将是构建文件夹。但是由于它们在两个存储库中分开并在两个 docker 容器或两个 ec2 实例中运行,我该如何集成它们?
  • 嗯,您需要某种 http 服务来为您的构建文件提供服务。它实际上不必是一个快速服务器,你甚至可以使用像 serve 这样的包来达到这个目的。因此,就集成而言,前端和后端通过 REST API 或任何其他技术(如 graphql)连接,连接它不会有问题。因此,指定静态文件夹并通过 express 提供服务只是托管构建反应应用程序的方法之一。超过 2 个 docker 容器,只需要暴露对应的端口并且容器必须在同一个网络中。
  • 如果它有两个 EC2 实例,可能应该启用 CORS,并且您可以使用私有 IP 从前端连接到后端。我希望答案和这两个 cmets 在一定程度上有助于澄清您的疑虑。
【解决方案4】:

1) 虚拟 DOM 基本上是说您正在调用一个反应函数,而不是对真实 DOM 进行操作的实际函数

喜欢这个

document.getElementById("demo").innerHTML ="Helloworld"

修改实际的dom

但是这个

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('demo')
);

如果你正确地看到了这一点,你并没有直接在 dom 上做任何事情,你只是让 react 函数控制做事,当 react 想要重新渲染它时,内部 react 会负责修改那个 dom 元素演示基于它自己的逻辑,这就是他们声称的优化,这就是人们首先使用它的原因。是的,当您使用 webpack 构建代码时,它确实包含 react,这是缩小代码的一部分,所以如果您在开发中看到任何错误堆栈跟踪,您确实会看到 react 是它的起点

2)我认为这是一个选择,因为对此没有限制

3) 开始部署,一般来说,如果你想使用 nodejs,你可能会选择 expressjs 服务器类型的部署,但通常最好使用 Nginx 或 Apache 之类的高性能服务器,或者如果你只是不想得到人们通常使用基于 Heroku 的部署,或者现在人们正在使用特殊的平台,例如 netlify、surge.sh(在这些平台上部署超级容易)。

【讨论】:

    【解决方案5】:

    我相信其他人在解释 React 虚拟 DOM 方面做得很好。以一种简单实用的方式,我将尝试解释我(将)如何使用 NodeJS 和 React 管理动态网站(包括中型企业系统)的部署。我也会尽量不让你厌烦。

    想象一下,React 从未存在过,而您正在构建一个传统的服务器端渲染应用程序。每次用户点击路由时,控制器都会与模型一起执行一些业务逻辑并返回一个视图。在 NodeJS 中,此视图通常使用诸如把手之类的模板引擎进行编译。如果您仔细考虑一下,很明显该视图可以是任何 html 内容,然后将其作为响应发送回浏览器。

    这是可以发回的典型响应:

    <html>
    
    <head>
        <title>Our Website</title>
        <style></style>
        <script src="/link/to/any/JS/script"></script>
    </head>
    
    <body>
        <h1>Hello World </h1>
    </body>
    
    </html>
    
    

    如果这个响应命中浏览器,屏幕上显然会显示“Hello World”。 现在,通过这种简单的方法,我们可以做强大的事情!

    选项 1: 我们可以指定一个控制器来处理所有传入路由app.get("*", controllerFunc),并为我们的整个服务器呈现一个视图。

    选项 2: 我们可以要求多个控制器处理不同的路由并从我们的服务器渲染特定于路由的视图。

    选项 3: 我们可以要求多个控制器处理不同的路由并从我们的服务器即时(即动态地)生成页面。

    如果我们要构建一个传统的 Web 应用程序,选项 3 将是唯一合理的标准。在这里,页面是为不同的路由动态生成的。但是,使用选项 1,我们可以生成一个高质量的单页应用程序,其中发送到服务器的响应是一个空的 html 页面,但使用具有操作 DOM 能力的内置 JS 脚本 - 是的,React强>!此类响应可能如下所示:

    <html>
    
    <head>
        <title>Our Website</title>
        <style></style>
        <script src="/link/to/any/JS/script"></script>
    </head>
    
    <body>
        <h1>Hello World </h1>
        <div id="root"> </div>
        <script async type=”text/javascript” src="/link/to/our/transpiled/ReactSPA.js"></script>
        <!--async attribute is important to ensure that the script has access to the DOM whenever it loads. This also makes the script non-blocking -->
    </body>
    
    </html>
    

    显然,我们将所有责任交给生成的 SPA,所有路由逻辑都在客户端处理(请参阅react-router-dom)。在服务器端,我们可以引入选项 2 中的概念并调整 NodeJS 路由处理程序以侦听任何 REST API 通信的另一个特定路由。如果你熟悉 NodeJS,app.get()app.post() 注册路由的顺序很重要。

    但是,使用选项 1,我们很快就会受到限制,并且只能从该服务器提供一个单页应用程序。为什么?因为我们已经要求一个控制器处理所有非 API 传入路由并呈现一个视图。我们还冒着提供不必要的臃肿 JS 文件的风险。当用户可能想要的只是登录页面时,他们将获得完整的网站。

    如果我们考虑选项 2,我们可以进行更多调整,并为不同的路由提供多个单页应用程序,所有这些都来自我们的服务器。这种方法有助于减少发送到浏览器的 JS 构建的大小。一个典型的例子是具有欢迎页面(或介绍目录)、登录页面和仪表板的网站。

    通过为不同的路由分配控制器,我们可以为这些路由构建唯一的 SPA。 SPA 用于介绍页面,另一个用于登录页面,然后另一个用于仪表板。是的,浏览器必须在三者之间转换时加载,但至少我们大大增加了网站的初始渲染时间。我们还可以使用更安全的 cookie 选项进行授权,而不是在 localStorage 上存储会话令牌的不太安全的选项。

    在更高级的设置中,我们可以将具有不同 React 组件的动态网站呈现为动态生成的页面中的小部件。实际上,这就是 Facebook 所做的。

    构建此类 SPA 或组件的方法非常简单。启动一个 react 项目并配置 webpack 以将生产就绪的 JS 文件呈现到服务器端 repo 中您首选的公共静态目录中。视图中指定的&lt;script&gt; 然后可以轻松加载这些构建的反应组件,因为它们存在于服务器端的公共目录范围内。

    本质上,这意味着一个包含多个客户端目录和一个服务器目录的 repo,其中 webpack 为每个客户端项目生成的生产构建文件的目标设置为服务器的公共静态目录。因此,每个客户端的目录都是一个项目(完整的 SPA 或简单的 React 组件作为小部件),具有自己的 webpack.config 和 package.json 文件。事实上,你可以有两个独立的配置文件——生产和开发。然后,要进行构建,您可以使用 npm ~relevant command~ 进行生产或开发构建。

    然后您可以继续以托管任何 NodeJS 应用程序的方式托管它。因为,主要应用程序是 NodeJS - 那是服务器所在的位置。用 PHP 和 Apache/NGINX 替换 NodeJS,概念还是一样的。

    【讨论】:

    • 感谢您的宝贵时间和回复。
    猜你喜欢
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2020-01-09
    • 2020-08-26
    • 2014-10-12
    • 1970-01-01
    • 2010-10-13
    • 2017-10-11
    相关资源
    最近更新 更多