65.9K
CodeProject 正在变化。 阅读更多。
Home

部署 React 应用到 Azure App Service - 教程和经验教训

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2020年11月13日

CPOL

19分钟阅读

viewsIcon

19258

downloadIcon

120

深入介绍了一些棘手的技术问题,以及个人思考

引言

我开始踏上这条学习之路时,几乎没有任何 Web 技术经验。我拥有的工具箱里有几把钝器——面向对象编程,对 HTML 的一些模糊概念,来自 Python 和 VB.NET 等语言的 API 调用,以及对套接字非常浅显的理解。我不知道接下来会遇到什么,只知道前端开发的世界将与我熟悉的桌面环境截然不同。

目录

选择技术栈

首先,我选择了一些想要深入学习的技术。由于 Web 开发是一个非常广阔的领域,涉及的参与者众多,一次性学习所有内容几乎是不可能的。因此,我(比喻性地)从帽子里抽签,只选择了少数几项来专注于此。

我知道 HTML 和 CSS 将满足我大部分的工作需求。即使有 JavaScript 等工具,任何网站的根基仍然是在浏览器中显示的超文本文件。幸运的是,这两者都有完善的文档,并且已经被详尽地写过。结合我对 XAML(微软的基于 XML 的用户界面语言)日益增长的喜爱,我能够轻易地接受它们的语法和用法。

然后,我需要选择一种 JavaScript。我想选择一种广泛使用、存在时间足够长以至于稳定且文档完善、并且在线教程足够多,以便我能够有条不紊地找到最终目标,并在过程中享受乐趣。我选择了 React.js,因为它提供了单页应用模型、JSX 以及其对有状态组件的有趣处理方式。

最后,我需要选择在哪里托管我的项目。一种可能性是,我将直接在我从大学的剩余物资商店里淘来的小型 Linux PC 上运行一个 npm 服务器。我以前也做过类似的事情,将 PC 用作家庭自动化物联网项目的“母舰”。然而,我最近获得了一个 Azure 认证,所以我想在这方面继续努力。我将把项目托管在 Azure App Services 上。

React 的快速学习心得

React 带来了许多我熟悉的其他语言所不具备的新可能性和限制。简单来说,JavaScript 看起来像 C#(或者任何你熟悉的 C 变体),行为像 Python,但又引入了一整套独特的其他功能。当我尝试构建我的第一个应用时遇到的第一个(也是最糟糕的)问题源于我顽固的老式思维:对象状态。

想象一下,你需要实现一个棋盘游戏——例如井字棋——在一个传统的桌面应用程序中。事实上,我最开始做的就是这个,因为我最近和一个正在学习编程的朋友一起做了一个编程练习。你会如何处理这个问题?

在我的面向对象思维中,解决方案很简单:创建一个 `Grid` 对象,它可以容纳一个 `Cell` 对象的二维数组。`Grid` 可以评估单元格的行、列或对角线是否处于危险或获胜状态,而 `Cell` 可以存储有关哪个玩家拥有它的信息。然后 `Cell` 可以决定它们如何渲染自身,而 `Grid` 简单地显示它。这样做的好处是 `Grid` 不需要知道 `Cell` 的实现方式,而 `Cell` 可以托管额外的行为,如悬停、显示警告、动画、忽略点击等。有了这些原型对象,理论上任何棋盘游戏都可以构建。

经过一段相当漫长的尝试后,我发现这种方法在 React 中实现需要进行大量的调整。‘对象’的概念相对抽象,实例方法也不能保证。换句话说,不能安全地假设调用一个对象的某个方法就会导致该对象被修改。

我必须认识到的最大变化是,Web 编程的基本理念不是让一百个秘密对象传递数据以供修改和润饰;一切都必须导致可见的、切实的改变。你不是将一个巨大的数据对象分发给许多函数,每个函数提取一个数据片段;相反,你有许多公共数据片段,每个片段都恰好去到它需要去的地方,不多也不少,向用户展示某种视觉差异。

我想,最终,当你的 Web 应用变得越来越复杂时,会出现一些情况,你可能需要开始重新回到面向对象编程的领域。然而,一种观点是,到那时,你的应用就太大了,你的逻辑最好还是放在服务器上。

当我开始整合这些内容时,我遇到了一些不显而易见的公理。React 中有一些优化是程序员必须理解并绕过的;如果不了解它们,应用程序似乎就会拒绝工作。我不想深入探讨 JavaScript 语言和 React 框架,因为这篇文章旨在帮助你拨开迷雾,所以我只简要介绍几点。

  • 首先,尽可能将你的应用状态提升到顶层。让一个主组件向下传递数据比让许多子组件各自拥有自己的状态更容易管理和故障排除。

  • 充分利用 props(属性)。子组件通过函数向父组件索要数据的模式已经过时了;每次需要构建子组件时,请将其传递下去。

  • 始终尽快设置组件的状态对象。React 会字面意义上地*响应*状态的变化来决定何时重绘组件。将尽可能多的数据存储在状态中,除非你有一些变化频率非常高但不需要重绘的内容。

  • 作为扩展,你可能会发现需要将某个特定函数绑定到实例。在构造函数中,使用 `this. = this..bind(this)` 来确保实例的方法始终可以访问同一实例的字段。

  • 使用 lambda 表达式。在应用流程的几乎任何地方,你都可以使用 lambda 表达式来替代原本无趣的字符串字面量。需要一个简单的函数来处理按钮点击?没必要将一个不被使用的命名函数添加到类中。`{()=> {}}` 是你的好朋友。

  • 对象类型不保证,这会带来麻烦。例如,我有一个由数字输入框控制的数字状态。你可能会认为数字输入框的值始终是数字——错了!尝试通过 `new Array(this.state.num)` 来实例化一个数组会失败。任何时候你必须确定一个值是数字,请确保你进行转换(在这种情况下,`Number(input.value)` 有效)。

  • 一些 React 函数是异步的,这意味着它们的执行是不可预测的。如果你有某个操作必须在异步调用之后执行,请将 lambda 表达式作为回调函数附加。对于任何可以等待的,或者可以在 `null` 引用异常中优雅失败的操作,请使用可选链。

  • 确保你将希望使用的任何包添加到 `package.json` 文件中,并运行 `npm install` 来自动下载它们。

  • 习惯使用 `document.getElementById()` 来用更专业的 JavaScript 对象替换模板 HTML 布局。

  • 最后,在尝试进行任何复杂操作之前,请务必了解不同的 HTML 标签。我在这方面肯定还在努力,并且经常对什么标签最合适感到困惑。使用在线参考资料不是作弊!

设置 Azure DevOps 和 App Service

完成了一个由 `create-react-app` 引导的单页应用后,我现在需要一个地方来展示它。虽然网上有一些关于为 Node.js 应用创建 Azure App Service 主机的教程,但我未能找到一个涵盖所有必要步骤使其运行起来的。也许这是因为 Azure 作为一个平台不断成长和演变,需求随时间变化,但我在这里将提供一个快速入门教程。

我假设你知道如何导航 GitHub,并且已经将你的代码上传到自己的仓库。你也可以访问我的仓库,看看我是如何实现我的项目的(你会发现一些测试以下所有说明的残留材料),以及我的项目网站

  1. 如果你还没有 Azure 账户,请创建一个。然后,创建一个新的 App Service 资源。选择你的订阅和资源组,如果需要可以创建一个新的。填写你的实例名称,选择“代码”作为发布选项,并选择一个 Node.js 运行时栈(因为我们正在构建一个 React 应用)。重要的是,选择一个你愿意使用的操作系统。根据你选择 **Linux** 还是 **Windows**,最后几个步骤会有所不同。选择一个离你近的区域,并暂时选择免费套餐。然后就可以完成创建过程了。

  2. 在你等待部署完成的同时,如果还没有 Azure DevOps 账户,请创建一个。你需要创建一个新项目(可能保持私有,除非你想公开)。或者,如果你有一个大项目中有许多构建管道,那也没问题——只要仔细跟踪所有内容。

  3. 你会看到一个 **Pipelines** 菜单项,还有一个名为 **Pipelines** 的子菜单项。打开它并创建一个新管道。在这里,你有两个选项——使用 **YAML 编辑器**来编写你的管道脚本,或者使用 **Classic 编辑器**来手动构建管道。使用 YAML 会在你的仓库中创建一个包含构建信息的文件,该文件将在每次提交新内容时运行。我将使用 Classic 编辑器,它更容易调整和自定义。如果你想使用 YAML 编辑器,请参阅下面的单独说明

  4. 打开经典编辑器并选择 GitHub 作为你的代码源。授权 DevOps 连接到你的 Github 账户。使用省略号 (...) 按钮帮助你找到包含你的 React 应用的仓库,然后选择你的分支。

  5. 当提示选择模板时,从“Empty Job”开始。然后你将看到一个编辑器,你可以在其中选择要添加到部署管道中的块。第一个节点已经为你设置好了,名为“Get sources”,它将下载仓库中的代码作为管道的第一步。第一个代理作业也为你加载了,但里面什么都没有。

    在撰写本文时,没有默认的 Node.js 部署到 Azure Web App 的选项,因此使用了 Empty Job。

  6. 需要添加三个或四个节点,具体取决于你之前选择的操作系统;这将在后面的步骤中进行解释。点击代理作业元素旁边的加号(**+**)按钮添加一个新步骤。会出现一个可搜索菜单——查找“`npm`”并选择 Microsoft 发布的最基础的选项。该元素然后会添加到你的作业中。选择它并修改它以运行“`install`”命令,忽略所有其他选项。

  7. 添加第二个“`npm`”元素。选择自定义命令并在命令框中输入“`run build`”。忽略所有其他选项。

  8. 添加一个“`Publish Artifact`”元素。你需要输入两个重要参数:要发布的路径,以及一个 artifact 名称。请仔细记录这两个参数。我分别推荐“*build*”和“*artifact*”,尽管默认的 artifact 名称是“*drop*”。

  9. 如果你为你的 app service 选择了一个 **Windows** 主机,你需要提供一个 `web.config` 文件来处理 IIS Web 服务器的路由选项。确保你已经创建了一个包含必要选项的文件(见示例),并将其放在仓库的某个位置——我将其放在了 `/src` 文件夹中。(如果你选择了一个 **Linux** 主机,可以跳过此步骤,但请注意在最后还有一个必须执行的步骤。)在 Publish Artifact 元素之前,立即添加一个“`Copy files`”元素,位于第三个位置。如果你的配置文件在根目录,你可以将源文件夹留空;否则,请指定其父目录。在内容框中输入“`web.config`”,在目标文件夹中输入你在步骤 8 中指定的发布路径——同样,建议的名称是 `build`。

  10. 保存你的管道!如果你不显式保存,你的工作将会丢失。

  11. 在导航栏上,转到 **Pipelines** > **Releases**。现在你将设置编译结果的发布过程。创建一个新的发布管道(而不是一个新的 release!),并再次从一个空的管道开始。在编辑器中,你会发现两个块,分别标记为 Artifacts 和 Stages——我们先添加一个 artifact。添加一个 artifact 并选择 **Build** 作为源类型。你需要选择源构建管道,也就是你在第 10 步保存的那个。将源别名修改成一个容易记住的名字,例如“*_artifact*”或“*pipeline-result*”。保存此设置。

  12. 返回发布编辑器,看到那两个框。你会注意到 artifact 元素右上角有一个闪电标志。点击它打开,并启用持续部署触发器开关。这将允许你的发布在构建管道完成时自动触发。忽略分支过滤器。

  13. 回到发布编辑器,通过点击“1 job, 0 task”链接打开 Stage 1。你会看到一个熟悉的管道编辑器。添加一个“**Azure App Service deploy**”元素。选择你的订阅(提示:选择一个服务连接,而不是实际的订阅)。根据你之前选择的操作系统选择你的 App Service 类型;**Windows** 或 **Linux** 上的 Web App。在列表中找到你的 App Service 名称。如果你看不到它,请确保你选择了正确的操作系统;确认你在 Azure 上的实际 App Service 资源具有你想要使用的操作系统。最后,编辑包/文件夹输入框。由于你还没有运行管道,浏览按钮将无法工作。相反,只需输入

    $(System.DefaultWorkingDirectory)/_artifact/artifact

    (并将末尾的两个目录替换为你的源别名(第 11 步)和 artifact 名称(第 8 步)对应的名称)。

  14. 最后,回到你在第 10 步的第一个构建管道。你现在可以打开并运行它。你可以跟着看 Agent job 1 如何完成 Node.js 包的安装,构建你的 Web 应用的生产版本,并将所有内容保存到 artifact 中。然后,如果你打开你的发布管道,你将看到它已被部署到你的 Azure App Service 资源。

  15. 如果你回到你的 App Service 资源并查看 Deployment Center,你会看到 Azure Pipelines 成功部署。如果你选择了 **Windows** 主机操作系统,你就可以开始了。访问你的网站将显示你的应用,如果你实现了 React 的路由,并且 `web.config` 配置正确,你的 React 应用也能处理这些导航。

  16. 如果你选择了 **Linux** 主机操作系统,现在访问你的网站将无法工作。这是因为你还需要配置最后一件事;Linux 主机需要使用 `pm2`,一个 JavaScript 进程管理器。你需要将其设置为在每次容器启动时运行。在 App Service 资源中,导航到 Configuration,General settings,然后输入这个启动命令

    pm2 serve /home/site/wwwroot --no-daemon --spa

    这会告诉主机将正确的目录作为你网站的根目录提供服务,并且 `--spa` 标志表示这是一个单页应用,以便路由自动指向你的根 `index.html` 文件,而不是实际搜索一个不存在的目录。保存此设置,然后重启你的 app service。你的网站现在应该可以正常工作了。

如果你选择使用 **YAML 编辑器**而不是经典编辑器,说明会稍短一些,但最终的设置会有点棘手。虽然这种方法可以几乎立即设置,但初始部署需要更长时间,以后调整起来更困难,而且你(目前)仅限于 Linux 主机。

  1. 从经典设置的第 3 步继续——选择你的仓库和项目,DevOps 会提示你选择一个管道配置。你可以选择快捷方式“*Node.js React Web App to Linux on Azure*”来设置大部分你需要的东西。不幸的是,在撰写本文时,没有 Windows 版本,并且没有简单的方法来修改自动生成的 YAML 文件使其在 Windows 主机上工作。选择你的 App Service,然后验证并配置。不要立即完成设置过程;还有一些设置需要更改。

  2. 如果你愿意,可以将 Stack 版本更改为 12 或 14;将文档末尾的 `NODE|10.10` 行更改为你想要的版本,例如 `NODE|12-lts`。检查启动命令;默认命令将不起作用。尝试立即访问你的 app service 页面将导致应用程序错误、502 或 404。**遵循 A、B 或 C 中的任何一个选项(只选一个!)**来修改 `StartupCommand` 属性并选择一个服务方法。选项 B 是我的推荐

    1. 开发构建方法:修改为 `'cd /home/site/wwwroot && npm run start'`。默认的命令将尝试启动一个开发服务器来托管你的代码,但它会尝试从根目录启动,而 `package.json` 不存在于那里。添加 change-directory 命令会将 npm 命令指向正确的位置。

      在免费套餐上,这需要相当长的时间才能启动,因为它会重新编译你的开发代码以进行可调试模式,所以请注意,你的网站可能需要几分钟才能可用。

    2. 进程管理器方法:更改为 `'pm2 serve /home/site/wwwroot/build --no-daemon --spa'`。这种方法,与经典设置的第 16 步一样,会启动一个 Web 服务器,从你应用程序的已构建版本提供服务。这种方法速度很快,并且在部署时提供更多选项,但需要更详细的命令。

    3. Npx Serve 方法:更改为 `'cd /home/site/wwwroot/build && npx serve'`。“*npx serve*”会在“serve”包不存在时自动下载并安装它,然后开始从你的构建目录提供你的生产构建。但是,如果你正在构建一个单页应用,你需要创建一个配置文件来重写路径到主应用。pm2 的 `--spa` 标志是一个更简单的方法,因此推荐使用选项 B 而不是选项 C。

  3. 保存你的管道!这将在你的仓库中插入一个 YAML 文件,并包含一个新的提交,该提交用于通知未来的部署。提交后,你应该会看到管道正在运行——你可以跟着看构建步骤是如何自动执行的。几分钟后(如果选择选项 A,可能会更长),你应该能够访问你的网站。

正如我之前提到的,到目前为止我在线上找到的所有教程都只描述了这些方法中的一种,而且通常不会解释使页面运行起来的最后几个配置步骤。希望我提供的说明能帮助你克服最后的障碍。

如果一切顺利,恭喜!你的 Web 应用现在对公众可用,并且与你对仓库的任何新提交都已连接。

最后提示一下,你可以修改持续集成触发器来忽略对 `README.md` 文件、YAML 配置等文件的更改,如果你愿意的话。如果你使用了经典编辑器,只需编辑你的管道,然后在 Tasks 和 Variables 旁边找到“**Triggers**”菜单项。然后你可以添加路径过滤器排除项,以忽略不应触发自动构建和部署的文件。如果你使用了 YAML 编辑器,编辑你的管道,并在顶部 Variables 和 Run 按钮旁找到省略号按钮。在省略号子菜单中,你会找到 Triggers 菜单项——覆盖触发器,并类似地添加排除项。在添加过滤器后,请确保保存!

设置 AWS 主机

很好——现在我已经向你展示了五种(经典 Windows、经典 Linux、YAML 开发构建、YAML pm2、YAML serve)部署 React 应用到 Azure App Services 的选项,现在是时候介绍第六种了:AWS Amplify。如果 Azure 不是你的菜,或者你想以最少的麻烦轻松完成设置,Amplify 可能会适合你。

我直说吧——我也尝试过将同一个应用部署到 Elastic Beanstalk,但我对它不够了解,无法使其正常工作。相反,我发现 Amplify 可能 anyways 是一个更好的选择。

  1. 如果你还没有 AWS 账户,请创建一个。搜索 Amplify 服务。在 Amplify 控制台主页上,选择“**Deploy**”。

  2. 选择 GitHub(如果你的仓库在那里)。完成 GitHub 授权,然后选择正确的仓库和分支。你可以保持构建设置不变。继续点击 Save and Deploy。

Amplify 应该会为你处理其余的事情。但是,与 Azure 不同的是,默认情况下你无法选择你的主机操作系统、定价层,或者拥有一个与你的项目名称相似的网站名称(需要自定义域)。你也无法更改触发器来过滤掉不需要的 CI 部署;它依赖于存储在你 GitHub 仓库设置中的 webhook 来通知 AWS 有新的提交被推送。你也许可以找到 webhook 事件的组合,例如 GitHub deployments 或 releases,来替代特定的文件排除。或者,你可以参考AWS 的文档来完全禁用自动构建,或者在任何不重要的提交中添加一个文本标签来跳过部署。

最终思考

这对我来说是一次学习经历。我不会撒谎,我仍然不确定我是否完全理解我在做什么。尽管如此,看到一个我一点一点拼凑起来的应用在一个公共网站上展示出来,总是令人欣慰的。我觉得看着它说“嘿!我做到了!我创造了它!”很有意义。在当前动荡的环境下(出于多种原因),拥有可以让你坐下来享受一点创造力并为自己的工作感到自豪的时刻,对于保持健康的心态至关重要。希望我能激励你做类似这样的项目,或者至少介绍一些你以前觉得遥不可及的新概念。

保重,一切安好!

历史

  • 2020 年 11 月 13 日:初版
© . All rights reserved.