TRANCE 堆栈
警告 此堆栈目前仅支持 typescript 和 NPM。
GitHub 操作脚本需要 NPM。我很快就会支持 pnpm 和 yarn,但这需要一些时间。在此之前,我希望能收到您对此堆栈的反馈。
包含的内容
这是一个 Remix 堆栈,提供了一种方式来发布生产就绪型的 Remix 应用程序。它的构建方式带有自己的观点,旨在用作 Remix 项目的起点。您可以根据自己的喜好对其进行修改,并将其用作 Remix 项目的基础。
📦 点击查看包含的技术列表
- 采用 CSP 和合理的认证流程来保证良好的安全实践
- 使用 i18next 及其 Remix 集成 remix-i18next 实现国际化 (i18n)
- Auth0 用于身份验证
- PostHog 用于功能标志
- Sentry 用于客户端错误跟踪 (服务器端即将推出)
- 定制的 cookie 同意横幅以最大限度地提高安全性 了解更多
- 分析集成
- 使用 TypeScript 进行静态类型化
- 使用 ESLint 进行代码检查
- 使用 Vitest 和 Testing Library 进行单元测试
- 使用 Playwright 进行端到端测试
- Conventional Commits 用于提交消息,以实现自动化版本控制
- semantic-release 用于自动发布管理
- Storybook v7 用于组件开发
- NPM 用于包管理 (目前为止。很快就会支持 yarn 和 pnpm)
- GitHub Actions 用于完整的 CI 设置
- AWS 部署,通过 GitHub Actions 使用 CDK
- 使用 AWS Lambda + Api Gateway + Cloud Front 进行生产构建
- 使用 AWS Lambda + Api Gateway 进行临时构建 (用于功能分支、拉取请求等)
- 使用 Renovate 自动更新依赖项
使用堆栈
使用堆栈创建您的项目
npx create-remix@latest --template meza/trance-stack my-app
设置过程将询问您 GitHub 存储库名称。如果您没有,别担心,您可以在设置过程结束后创建它。
警告
从现在起 在您自己的项目目录中阅读此文档。它将包含与您相关的链接,因为 init 脚本将用自定义到您项目的链接替换此自述文件中的链接。
现在启动开发服务器
npm run dev
这将为您设置一个默认的 Remix 应用程序。在完成设置过程之前,它将无法正常工作。您可以在 此处 找到该过程的说明
快速入门
- 安装依赖项
npm install
- 启动开发服务器
npm run dev
- 浏览 入门 部分以设置本地和部署环境
值得注意的 npm 脚本
npm run ci
- 运行与 CI 上运行的相同的验证脚本npm run clean
- 删除所有生成的文件npm run clean:all
- 删除所有生成的文件和所有 node_modules 目录npm run dev
- 启动开发服务器npm run deploy:dev
- 将应用程序部署到临时环境npm run deploy:prod
- 将应用程序部署到生产环境 (您可能永远不应该在本地使用它)npm run int
- 运行 Playwright 集成测试npm run report
- 运行为您生成报告的所有内容 (覆盖率、cpd、loc 等)npm run storybook
- 启动 Storybook 服务器npm run validate
- 运行 CI 测试和集成测试
目录
- 包含的内容
- 使用堆栈
- 快速入门
- 目录
- 入门
- 如何使用 ...?
入门
为了使该项目正常工作,您需要先设置一些内容。
该堆栈的设计方式使您可以轻松地移除不需要的部分。您可以在每个步骤中找到移除说明,因此,如果您不喜欢某个特定服务,请不要担心。
但是...为什么?
注意 在整个项目开发过程中,我们一直在使用 架构决策记录,因此,如果您发现自己想知道为什么我们选择了某个特定服务或实现,您可以在 ADR 页面中找到更多信息。
我们强烈建议您继续添加自己的决策。这是记录项目历史背景的好方法,也是与团队其他成员分享知识的好方法。
我们使用 adr-tools 来管理我们的 ADR。它作为依赖项的一部分安装,因此您应该能够立即使用它。
环境
检查项目根目录是否有 .env
文件。如果没有,将 .env.example
文件复制到 .env
cp .env.example .env
此文件包含项目正常运行所需的所有变量。
APP_DOMAIN
通常应该保持不变。它是你的应用程序将要从其提供的域名。这个变量也由部署脚本设置,所以你不用担心它。在本地开发过程中,它将被设置为 https://127.0.0.1:3000
。
NODE_ENV
变量用于确定你正在运行的应用程序环境。看起来 ARC 自己很难弄清楚,所以我们设置了手动设置它。如果一切顺利,它很快就不需要了。
SESSION_SECRET
变量用于加密会话 Cookie。它应该是一个很长、随机的字符串。
GitHub 设置
注意该项目使用 GitHub Actions。如果你不熟悉 GitHub Actions,你可以阅读更多相关信息 这里。
你需要做几件事来确保 GitHub Actions 可以与你的项目一起工作。
工作流权限
首先,转到 https://github.com/meza/trance-stack/settings/actions,在 工作流权限
部分,确保它设置为 读写权限
选项。
没有它,部署脚本将无法创建必要的 GitHub 版本。
分支保护
接下来,转到 https://github.com/meza/trance-stack/settings/branches 并添加一些分支保护规则。
- main
- alpha
- beta
这些是将用于应用程序的不同阶段的分支。你可以根据自己的喜好设置这些分支的设置,但有一个设置你需要确保未选中:允许删除
。
我们在后面的 部署 部分使用它来防止命名环境被删除。
Pages
接下来,转到 https://github.com/meza/trance-stack/settings/pages 并确保 来源
设置为 GitHub Actions
。这将允许我们将项目的 storybook 部署到 GitHub Pages。
环境
注意我们使用 GitHub 环境来管理应用程序的不同阶段。你可以阅读更多相关信息 这里。
GitHub 环境非常适合控制工作流中使用的环境变量。
现在,转到 https://github.com/meza/trance-stack/settings/environments 并创建以下环境
生产
Staging
Ephemeral
这些在 部署工作流 中被引用,例如使用 environment
键。Ephemeral
环境用于功能分支和拉取请求,并在 临时工作流 中被引用。
变量与 Secrets
一些配置值是敏感的,而另一些则不是。例如,COOKIEYES_TOKEN
不是敏感的,但 AUTH0_CLIENT_SECRET
是。这主要是因为这些值中的一些会被嵌入到应用程序的 html 中,并对每个人可见。
警告请仔细检查服务的文档,以确保你正确设置它们。
如果你将一个 Secret 作为变量添加,或将一个变量作为 Secret 添加,应用程序将无法正常工作。
GitHub Token - 首先这样做!
为了使版本发布正常工作,你需要 创建个人访问令牌。它需要以下设置
- 过期时间:永不过期
- 范围
- repo 用于私有仓库
- public_repo 用于公共仓库
创建令牌后,转到 secrets 设置 并将其添加为 GH_TOKEN
持续部署设置
部署过程在 部署 部分有描述,但为了让你开始,请创建 环境变量 部分中定义的环境变量和 Secrets。
使用 Auth0 进行身份验证
我们使用 Auth0 进行身份验证。你需要创建一个 Auth0 帐户,并 设置一个应用程序。
创建新应用程序时,请确保设置以下设置
- 应用程序类型应为
常规 Web 应用程序
- 忽略快速入门部分
- 转到设置,复制
域名
和客户端 ID
和客户端 Secret
,并将其粘贴到.env
文件中 - 将令牌端点身份验证方法设置为
Post
- 转到
允许的回调 URL
部分,添加https://127.0.0.1:3000/auth/callback
- 转到
允许的注销 URL
部分,添加https://127.0.0.1:3000
- 转到
允许的 Web 来源
部分,添加https://127.0.0.1:3000
- 转到
允许的来源 (CORS)
部分,添加https://127.0.0.1:3000
- 转到
刷新令牌轮换
部分,启用它,并启用绝对到期时间
选项。
将 Auth0 变量添加到 GitHub
现在你有了 Auth0 变量,你需要将它们添加到上面创建的 GitHub 环境中。
转到 secrets 设置,并使用与 .env
文件中的变量相同的名称添加 Auth0 Secrets。
如果你愿意,可以为每个环境设置自定义值。例如,你可以将 AUTH0_DOMAIN
设置为 dev-123456.eu.auth0.com
用于 Staging
环境,并将 prod-123456.eu.auth0.com
设置为 Production
环境。
但为了简单起见,你只需要在 Actions Secrets 的主页面中设置一次相同的值,它将用于所有环境。
启用 Auth0 集成以进行功能分支/PR 部署
如果你想为功能分支/PR 部署启用 Auth0 集成,你需要执行一些额外的步骤。由于功能分支/PR 部署是临时的,因此每次部署它们时,它们都会有不同的域名。这意味着你需要将域名添加到 允许的回调 URL
和 允许的注销 URL
中。
为了使这一过程变得简单,我们可以在域名中使用 *
通配符。这将允许使用任何域名。
在上面的初始设置过程中,你已经在几个地方添加了 https://127.0.0.1:3000
。你需要将 ,https://*.execute-api.us-east-1.amazonaws.com
添加到相同的位置。(注意开头的逗号。域名需要用逗号隔开)
注意你需要将
us-east-1
部分替换为你使用的区域。
例如,允许的回调 URL 部分应该如下所示
https://127.0.0.1:3000/auth/callback,https://*.execute-api.us-east-1.amazonaws.com/auth/callback
警告
*
通配符将允许你使用尽可能广泛的域名。但是,这以安全为代价。我们强烈建议你在 Auth0 上为你的功能分支/PR 部署创建一个备用租户。
从应用程序中删除 Auth0 集成
- 从
.env
文件和 GitHub Secrets 中删除AUTH0_DOMAIN
、AUTH0_CLIENT_ID
和AUTH0_CLIENT_SECRET
变量。 - 删除
src/auth.server.ts
和src/auth.server.test.ts
文件。 - 从
package.json
文件中删除auth0-remix-server
依赖项。 - 按照编译和测试错误删除所有使用
auth0-remix-server
依赖项的代码。
Google Analytics 4 集成
我们使用 Google Analytics v4 进行分析。你需要创建一个 Google Analytics 帐户并 设置一个属性。
完成属性设置后,你需要复制数据流的 测量 ID
,并将 GOOGLE_ANALYTICS_ID
变量粘贴到 .env
文件中。
你还需要转到 变量设置,并添加与 .env
文件中相同的变量名称。
警告
GOOGLE_ANALYTICS_ID
被设置为 Actions 的变量。
从应用程序中删除 Google Analytics 4 集成
- 从
.env
文件和 GitHub 变量中删除GOOGLE_ANALYTICS_ID
变量。 - 删除
src/components/GoogleAnalytics
目录。 - 从
src/types/global.d.ts
文件中的appConfig
类型中删除相关的类型。 - 从
src/root.tsx
文件中删除<GoogleAnalytics ... />
组件及其导入。 - 运行
vitest --run --update
来更新快照。
Hotjar 集成
我们使用 Hotjar 用于热图和用户录制。你需要创建一个 Hotjar 帐户并设置一个新网站。
完成网站设置后,转到 https://insights.hotjar.com/site/list,复制网站 ID,并将 HOTJAR_ID
变量粘贴到 .env
文件中。
你还需要转到 变量设置,并添加与 .env
文件中相同的变量名称。
警告
HOTJAR_ID
被设置为一个变量 用于操作。
从应用程序中删除 Hotjar 集成
- 从
.env
文件和 GitHub 变量中删除HOTJAR_ID
变量。 - 删除
src/components/Hotjar
目录。 - 从
src/types/global.d.ts
文件中的appConfig
类型中删除相关的类型。 - 从
src/root.tsx
文件中删除<Hotjar ... />
组件及其导入。 - 运行
vitest --run --update
来更新快照。
PostHog 集成
我们使用 PostHog 进行分析。你需要创建一个帐户并设置一个新项目。
项目设置完成后,前往 https://posthog.com/project/settings 复制项目的 API 密钥,并在 .env
文件中设置 POSTHOG_TOKEN
变量。你也要设置 POSTHOG_API
变量为 https://eu.posthog.com
或 https://posthog.com
,具体取决于你的数据驻留偏好。
你还需要前往 变量设置 添加与 .env
文件中相同的变量名称。
区分环境
在 PostHog 中,你的主要单元称为组织。一个组织可以有多个“项目”,这些项目本质上是环境。例如,你可以拥有一个 production
项目和一个 staging
项目。
这使你可以为每个环境拥有不同的功能标志、用户和数据。随时为每个环境创建一个新项目,然后设置相应的环境变量。
从应用程序中删除 PostHog 集成
- 从
.env
文件和 GitHub 变量中删除POSTHOG_TOKEN
和POSTHOG_API
变量。 - 删除
src/components/Posthog
目录。 - 从
src/types/global.d.ts
文件中的appConfig
类型中删除相关的类型。 - 从
src/root.tsx
文件中删除<Posthog ... />
组件及其导入。 - 运行
vitest --run --update
来更新快照。 - 从
package.json
文件中删除posthog
依赖项。 - 根据编译和测试错误删除使用
posthog
依赖项的所有代码。
Renovate 机器人设置
我们使用 Renovate 来管理依赖项更新。要利用它,你需要安装 Renovate GitHub 应用.
首先,导航到 https://github.com/apps/renovate 并点击安装按钮。
在下一个屏幕上,我们建议选择“所有仓库”以简化操作,但你可以将其配置为仅在你当前的仓库中工作。
Sentry 集成
注意 由于与 Architect 的兼容性问题,Sentry 的服务器端仪器目前无法正常工作。请关注 此问题 以获取更新。相关代码已在
entry.server.tsx
文件中注释掉。
我们使用 Sentry 进行错误报告。你需要创建一个帐户并设置一个新项目。
项目设置完成后,前往项目设置并复制 DSN
,然后将其粘贴到 .env
文件中的 SENTRY_DSN
变量中。
你还需要转到 变量设置,并添加与 .env
文件中相同的变量名称。
接下来,前往 https://sentry.io/settings/account/api/auth-tokens/ 创建一个新令牌。你需要 project:releases
和 project:read
权限。
获取令牌后,前往 秘密设置 添加以下内容:
SENTRY_AUTH_TOKEN
- 你刚刚创建的令牌SENTRY_ORG
- 组织标识SENTRY_PROJECT
- 项目标识
我们将使用这些令牌将源映射发送到 Sentry,以便错误正确映射到源代码。
部署脚本将自动将源映射上传到 Sentry,然后将其从本地删除,因此它们不会上传到环境中。
如何查找 DSN
首先,前往项目设置
然后在侧边栏中点击 客户端密钥 (DSN)
最后,复制 DSN
值
从应用程序中删除 Sentry 集成
- 从
.env
文件和 GitHub 变量中删除SENTRY_DSN
变量。 - 运行
npm remove @sentry/*
删除所有 sentry 包。 - 从
src/types/global.d.ts
文件的appConfig
中删除sentryDsn
,从ProcessEnv
类型中删除SENTRY_DSN
。 - 在
src/root.tsx
文件的最底部,将withSentry(App)
替换为App
。 - 从
src/entry.client.tsx
和src/entry.server.tsx
文件中删除Sentry.init
调用。 - 根据编译和测试错误删除使用 Sentry 的所有代码。
- 打开
.github/workflows/deploy.yml
和.github/workflows/ephemeralDeply.yml
文件,删除Sentry Sourcemaps
步骤。
如何使用...?
本节将深入探讨堆栈中存在的概念。
身份验证
身份验证通过 auth0-remix-server 包完成。该包中的 README 文件包含了解其工作原理所需的所有信息。
自动语义版本控制
我们使用 Conventional Commits 自动确定包的下一个版本。它使用 semantic-release 包来自动执行版本控制和发布流程。
功能由 .releaserc.json
文件控制。由于从该堆栈创建的项目很可能不是 npm 库,因此 npm 发布插件未包含在配置中。
要有效地使用常规提交,你需要了解以下基本原则
你的提交消息决定了是否向生产环境进行新部署。
触发构建的消息是
fix: ...
- 修复错误feat: ...
- 添加新功能
不会触发新版本(因此不会触发构建)的消息是
docs: ...
- 文档更改chore: ...
- 对构建流程或辅助工具和库(如文档生成)的更改refactor: ...
- 既不修复错误也不添加功能的代码更改style: ...
- 不影响代码含义的更改(空格、格式、缺少分号等)test: ...
- 添加缺少的测试或更正现有测试ci: ...
- 对 CI 配置文件和脚本的更改perf: ...
- 提高性能的代码更改
带有语义版本控制的分支策略
我们将在 部署 部分讨论部署是如何工作的。现在,让我们看一下分支策略如何与版本控制配合使用。
有 3 个主要分支
main
- 这是主分支。它是部署到生产环境的分支。beta
- 这是部署到测试(预发布)环境的分支。alpha
- 这是部署到 alpha(预发布)环境的分支。
当你推送到 main
分支时,会发布一个新版本到生产环境。版本由提交消息确定,推送到 main
分支的每个提交都会触发一个新版本。
当你推送到 alpha
或 beta
分支时,会创建一个新的 预发布 版本。这使你可以对即将发布的功能进行迭代,而不必担心每次推送引入新功能或修复的提交时都增加版本号。
例如,如果你在生产环境中有一个 1.0.0
版本,并将提交推送到 alpha
分支,那么版本将是 1.1.0-alpha.0
。如果你将另一个提交推送到 alpha
分支,那么版本将是 1.1.0-alpha.1
,依此类推。
当你将 alpha
或 beta
分支的拉取请求合并到 main
分支时,这些分支中的所有更改都将收集起来并打包到一个单独的版本中。继续上面的例子,如果你在生产环境中有一个 1.0.0
版本,并将 alpha
分支及其 1.1.0-alpha.1
版本合并,那么你在生产环境中新创建的版本将是 1.1.0
。
---
title: Branching & Versioning
---
%%{title: '', init: {'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
gitGraph
commit id: "v1.0.0"
branch feature order: 2
branch alpha order: 1
checkout feature
commit id: "fix: x"
commit id: "fix: y"
checkout alpha
merge feature id: "v1.0.1-alpha.1"
checkout feature
commit id: "fix: z"
checkout alpha
merge feature id: "v1.0.1-alpha.2"
checkout feature
commit id: "feat: added something cool"
commit id: "fix: fixed a mistake"
commit id: "refactor: refactored the tests"
checkout alpha
merge feature id: "v1.1.0-alpha.1"
checkout main
merge alpha id: "v1.1.0"
代码风格检查
我们使用 commitlint 来检查提交消息。配置位于 package.json
文件中。代码风格检查会在你进行提交时触发。如果提交消息不符合常规提交格式,提交将失败。
代码风格检查本身由 lefthook 触发
我正在运行哪个版本?
应用的版本被发送到 <html data-version="...">
属性中。你可以使用它来确定在任何给定环境中运行的是应用的哪个版本。
Cookie 同意
我们已经构建了一个自定义的 Cookie 同意解决方案,它与安全的 XSS 防护实践以及欧盟 Cookie 法律兼容。
注意 你可以在 Cookie 同意 ADR 中了解更多信息。
解决方案位于 src/components/CookieConsent
文件夹中,它应该被修改以符合你的需求。
当你打开 _index.tsx
文件时,你会看到以下接口
interface ConsentData {
analytics?: boolean | undefined;
//add your own if you need more
// marketing?: boolean | undefined;
// tracking?: boolean | undefined;
}
interface CookieConsentContextProps {
analytics?: boolean | undefined;
setAnalytics: (enabled: boolean) => void;
//add your own if you need more
// marketing?: boolean | undefined;
// setMarketing: (enabled: boolean) => void;
// tracking?: boolean | undefined;
// setTracking: (enabled: boolean) => void;
}
你需要修改这些接口以添加你特定的 Cookie 类型。例如,如果你想添加一个 marketing
Cookie,你需要添加以下内容
interface ConsentData {
analytics?: boolean | undefined;
marketing?: boolean | undefined;
}
interface CookieConsentContextProps {
analytics?: boolean | undefined;
setAnalytics: (enabled: boolean) => void;
marketing?: boolean | undefined;
setMarketing: (enabled: boolean) => void;
}
使用同意提供者
为了遵守 Cookie 同意,你需要识别项目中添加特定类型 Cookie 的元素。
此堆栈中的一个很好的例子是 GoogleAnalytics 组件。它位于
src/components/GoogleAnalytics
中
Cookie 同意提供者在 root.tsx
文件中使用,因此它对你的所有组件都可用。要使用它,你只需要
const { analytics } = useContext(CookieConsentContext);
if (analytics) {
//add your analytics code here
}
依赖项版本更新
我们使用 Renovate 自动更新依赖项。配置位于 .github/renovate.json
文件中。
默认情况下,它被配置为根据一些基本规则更新依赖项。
运行时依赖项
运行时依赖项是
package.json
文件中的dependencies
部分。
运行时依赖项是我们用来运行应用程序的库。这也意味着安全性和错误修复对这些依赖项很重要。
我们希望尽快更新这些依赖项,因此我们有以下配置。
次要版本和补丁版本
- 创建一个提交消息中带有fix:
前缀的拉取请求,并在可能的情况下自动合并。主要版本
- 创建一个提交消息中带有fix:
前缀的拉取请求,并且 **不要** 自动合并。
开发依赖项
开发依赖项是
package.json
文件中的devDependencies
部分。
开发依赖项是我们用来开发应用程序的库。这意味着我们不需要在更新这些依赖项时发布新版本的应用程序。
我们仍然希望尽快更新这些依赖项,因此我们有以下配置。
次要版本和补丁版本
- 创建一个提交消息中带有chore:
前缀的拉取请求,并在可能的情况下自动合并。主要版本
- 创建一个提交消息中带有chore:
前缀的拉取请求,并且 **不要** 自动合并。
部署
此堆栈的主要重点之一是创建一种部署策略,为任何从该堆栈构建的人提供一个良好的起点。
我们结合使用 GitHub Actions 和 AWS CDK 将应用程序部署到生产环境和临时环境。
临时环境
临时环境是在需要时创建的,并在不再需要时销毁的。我们将其用于功能分支和拉取请求。
它们会为拉取请求自动创建,但如果您只想部署功能分支,则需要手动触发一个。
手动临时部署
导航到 https://github.com/meza/trance-stack/actions/workflows/ephemeralDeploy.yml 并点击“运行工作流”按钮。
选择分支后,它将开始构建应用程序并将其部署到临时环境。
完成后,它将在运行的摘要仪表盘上发布一个摘要,其中包含部署应用程序的链接。它将看起来像这样。
拉取请求临时部署
创建拉取请求时,GitHub Actions 将自动为您创建一个临时环境,并且部署链接将作为评论添加到拉取请求中。
类似生产的环境
类似生产的环境是在创建一次后,在应用程序更新时更新的环境。
分支 main
被认为是生产分支,而 alpha
和 beta
被认为是预发布阶段。
这在 deploy.yml
文件中决定。
build:
environment: ${{ github.ref_name == 'main' && 'Production' || 'Staging' }}
这里的“生产”和“预发布”直接引用我们配置的 GitHub 环境。
警告 这意味着
alpha
和beta
分支都将部署到预发布
环境。
这样做是为了堆栈的方便,但强烈建议您根据需要进行更改。也许添加一个单独的 alpha
环境?
注意 请记住,GitHub 环境包含用于该工作流的环境变量。这意味着您可以为每个环境设置不同的
APP_URL
,以及其他内容,例如单独的 Auth0 租户。
GitHub Actions
GitHub Actions 对存储库生命周期中的各种事件做出响应。下面的图表显示了部署过程的流程。
flowchart TD
F1 -.->|Manual Trigger| F
subgraph Push
A[Push] --> D{Is Protected Branch?}
D -->|Yes| H{Is it the 'main' branch?}
D -->|No| F1[Offer Manual Ephemeral Deployment]
H -->|Yes| I1{{Deploy to Production}}
H -->|No| I2{{Deploy to Staging}}
I1 --> J1[Create GitHub Release]
H -->|Yes| J2[Deploy Storybook]
I2 --> J1
end
subgraph Pull Request
B[Pull Request] --> F{{Ephemeral Deployment}}
end
subgraph Cleanup
C[Delete Branch] --> X{{Destroy Deployment Stack}}
end
六边形节点是由 CDK 执行的进程,而其他节点则由 GitHub Actions 处理。
CDK
AWS 云开发工具包 (CDK) 是一个开源软件开发框架,用于在代码中定义云基础设施并通过 AWS CloudFormation 进行配置。
注意 如果你对我们为什么选择 CDK 感兴趣,请查看 相关 ADR。
大多数基础设施是在 deployment
目录中定义的。deployment/lib
目录包含用于构建基础设施的自定义 构造。
环境变量
要部署应用程序,您需要设置以下环境变量。
变量 | 密钥 | 描述 |
---|---|---|
AWS_ACCESS_KEY_ID | 用于部署应用程序的 AWS 访问密钥 ID。 | |
AWS_CERT_ARN | 用于域的证书的 ARN。 | |
AWS_SECRET_ACCESS_KEY | 用于部署应用程序的 AWS 秘密访问密钥。 | |
AWS_DOMAIN_NAME | 应用程序的最终域名。 | |
AWS_HOSTED_ZONE_NAME | Route53 中托管区域的名称。 |
如果您从文档顶部到这里,返回您所在的位置 并从那里继续。
本地环境
如果您想在本地部署应用程序,您只需要设置 AWS_ACCESS_KEY_ID
和 AWS_SECRET_ACCESS_KEY
环境变量。
部署目录
deployment/stacks
目录包含实际部署到 AWS 的堆栈。它们的命名应该是自解释的。我们有一个用于 临时
环境,一个用于 生产
环境。
如果您检查任一部署文件,您会注意到部署基本上是一个单一命令。
npx cdk deploy remix-trance-stack-ephemeral -O /tmp/deployment.result.json \
--require-approval never \
--context environmentName=${{ env.REF_NAME }} \
--context domainName=${{ vars.AWS_DOMAIN_NAME }} \
--context certificateArn=${{ secrets.AWS_CERT_ARN }} \
--context hostedZoneName=${{ vars.AWS_HOSTED_ZONE_NAME }}
临时部署和生产部署之间的区别在于堆栈的名称。它可以是 remix-trance-stack-ephemeral
或者 remix-trance-stack-production
。
上下文变量
上下文变量用于将信息传递给 CDK 堆栈。
变量 | 描述 | 示例 |
---|---|---|
environmentName |
环境的名称。这用于创建派生在 AWS 上创建的每个资源的名称。 | feature1 |
domainName |
应用程序的域名。 | trance-stack.vsbmeza.com |
certificateArn |
用于应用程序的证书的 ARN。 | arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 |
hostedZoneName |
用于应用程序的托管区域的名称。 | vsbmeza.com |
domainName
、certificateArn
和hostedZoneName
仅用于生产部署。
注意 即使某些上下文变量仅用于生产部署,它们仍然传递给临时部署。这是因为 CDK 堆栈对于两种环境都是相同的,并且上下文变量的评估是在运行时完成的。对于临时部署,您可以为
domainName
、certificateArn
和hostedZoneName
设置空字符串。
从本地机器部署
我们建议您使用 GitHub Actions 部署应用程序。但是,如果您想从本地机器部署,您可以通过运行与部署脚本相同的命令来实现。
警告 不要忘记在部署之前运行
npm run build
。
您可以在命令行上定义上下文变量,或者可以使用 cdk.context.json
文件。
{
"environmentName": "localdev",
"domainName": "trance-stack.example.com",
"hostedZoneName": "example.com",
"certificateArn": "arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012"
}
githubActionSupport.ts 文件
让我们谈谈 githubActionSupport.ts
文件。
此文件使用 GitHub Actions 工具包,使我们能够将部署 URL 报告回 GitHub Actions/拉取请求。
它之所以比实际需要更复杂,是因为我们不想在每次部署同一个分支时都发布 PR 评论。由于已部署分支的 URL 不会改变,因此没有必要向 PR 发送垃圾邮件。
这带来了一个挑战,即找到现有的部署评论并更新它,而不是创建一个新的。
在本地测试 GitHub 支持
如果您出于任何原因想获得 GitHub Actions 支持的本地输出,您可以通过运行以下命令来实现。
npx ts-node --prefer-ts-exts deployment/githubActionSupport.ts /tmp/deployment.result.json
这要求您在 /tmp
目录中有一个 deployment.result.json
文件。您可以通过运行 本地部署命令 来获取此文件。
结果将被添加到 deploymentSummary.md
文件中。
环境变量
环境变量可能是这个项目维护中最大的痛点。您必须将它们添加到 GitHub,将它们添加到部署脚本中,并将它们添加到 .env
文件中。
我们正在努力解决这个问题,但目前,您必须手动执行。
添加新环境变量清单
将变量添加到...
.env
文件.env.example
脚本。**这一点非常重要**.github/workflows/deploy.yml
脚本到npm run build
命令.github/workflows/ephemeralDeploy.yml
脚本到npm run build
命令.github/workflows/ephemeralDestroy.yml
脚本到npm run build
命令.github/workflows/playwright.yml
脚本到“创建 Envfile”部分
捆绑环境变量
我们将大多数环境变量捆绑到服务器包中。要了解原因,请阅读 相关 adr,以及 它的附录。
需要了解的是,捆绑的内容是通过读取 .env.example
文件并获取其键来决定的。
您可以通过将某些键添加到 remix.config.js
文件中的拒绝列表中来阻止它们被捆绑。
const doNotBundleEnv = [
'APP_DOMAIN' // deny list for the environmentPlugin
]
功能标志
功能标志是一种在生产环境中测试新功能的绝佳方法,而不必担心会破坏任何东西。它使您能够将新代码的发布与新功能的发布分离。 了解更多
让我们看一个位于 src/routes/_index.tsx
文件中的示例。
export const loader: LoaderFunction = async ({ request, context }) => {
const isAuth = await hasFeature(request, Features.AUTH);
return json({
isHelloEnabled: await hasFeature(request, Features.HELLO),
isAuthEnabled: isAuth
});
};
export default () => {
const { isHelloEnabled, isAuthEnabled } = useLoaderData<typeof loader>();
if (isHelloEnabled) {
return (<div>
<Hello/>
{isAuthEnabled ? <Login/> : null}
</div>);
}
return <div>Goodbye World!</div>;
};
这里,页面中的所有元素都包裹在一个功能标志中。Hello
组件只有在启用 HELLO
功能时才会渲染。Login
组件只有在启用 AUTH
功能时才会渲染。
区分环境
在 PostHog 中,你的主要单元称为组织。一个组织可以有多个“项目”,这些项目本质上是环境。例如,你可以拥有一个 production
项目和一个 staging
项目。
这使你可以为每个环境拥有不同的功能标志、用户和数据。随时为每个环境创建一个新项目,然后设置相应的环境变量。
I18N - 国际化
我们使用 i18next 进行国际化。您可以在 i18next 文档 中了解更多信息。为了将其与 Remix 集成,我们使用 remix-i18next 包,我们的设置基于 remix-i18next 自述文件。
您可以在 src/i18n
目录中找到 i18n 配置。i18n.config.ts
文件包含 i18next 默认值的配置。i18n.server.ts
文件包含服务器端的配置,而 i18n.client.ts
文件包含客户端端的配置。
我们与 remix-i18next 示例设置的唯一区别是,我们实际上将翻译打包到服务器包中。这是在 src/i18n/i18n.server.ts
文件中完成的。
await i18nextInstance.init({
debug: process.env.I18N_DEBUG === 'true',
...baseConfig,
lng: locale,
ns: remixI18next.getRouteNamespaces(remixContext),
// The sample setup in remix-i18next
//backend: {
// loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
//},
resources: {
en: {
translation: en
}
}
});
我们之所以这样做,是因为在 AWS Lambda 环境中,我们只有一个文件作为处理程序,并且它需要是自包含的。虽然传统的 lambda 函数可以访问附加的文件系统,但这会使部署更加复杂,并且函数将与 Lambda@Edge 解决方案不兼容。
因此,我们没有使用 fs-backend
,而是直接从 public/locales
目录导入资源。
这意味着,当您添加新的语言环境时,您必须将其添加到 i18n.server.ts
文件中的资源中。
使用翻译
要在您的应用程序中使用翻译,您可以使用 react-i18next
包中的 useTranslation
钩子。
import { useTranslation } from 'react-i18next';
export const Hello = () => {
const { t } = useTranslation();
return (
<h1 data-testid={'greeting'} className={'hello'}>{t('microcopy.helloWorld')}</h1>
);
};
您还可以 传入变量 到翻译中。这有助于翻译人员创建更多上下文相关的翻译。
以应用程序初始登录的仪表板为例
export default () => {
const { t } = useTranslation();
const { user } = useLoaderData<typeof loader>();
return (<>
<div>{t('dashboard.for', { name: user.nickname || user.givenName || user.name })}<br/><Logout/></div>
</>);
};
这里,我们将 name
变量传入翻译。这意味着名称在最终文本中的位置在不同的语言中可能会有所不同。例如,在一个上下文中,我们可以说“John 的仪表板!”,而在另一个上下文中,我们可以说“John 的仪表板!”。
我们仪表板中的翻译文件如下所示
{
"dashboard": {
"for": "Dashboard for {{ name }}"
}
}
添加新的语言环境
要添加新的语言环境,您需要执行以下操作
- 将新的语言环境添加到
public/locales
文件夹中。请参考现有语言环境的示例 - 将新的语言环境添加到
i18n.server.ts
文件中的resources
对象中。 - 将新的语言环境添加到
i18n.config.ts
文件中的supportedLngs
数组中。
从您的项目中移除 i18n
如果您不想使用 i18n,可以从您的项目中移除它。您需要执行以下操作
- 从
src
目录中移除i18n
文件夹 - 从
public
目录中移除locales
文件夹 - 运行
npm remove i18next i18next* *i18next
- 从
src/entry.server.tsx
和src/entry.client.tsx
文件中移除<<I18nextProvider ...>
- 按照编译错误并移除任何剩余的 i18n 引用
注意
在 i18n 自述文件 中有一些关于组织翻译的绝佳技巧。
Lefthook
提交验证和自动依赖安装由 Lefthook 完成
配置文件位于 .lefthook.yml
中。您可以查看所有发生的命令以及它们附加到的 git 钩子。
如果在每次提交时运行所有测试过于繁琐,您可以将其设置为在 pre-push 时发生。
NPMIgnore - 自动化
如果您希望将您的项目发布到 NPM(您不应该这样做),您可以使用 npmignore 包自动生成 .npmignore
文件。该文件将基于 .gitignore
文件生成。
package.json
文件的 publishConfig
部分中有一个基本的忽略配置。
Playwright - 端到端测试
我们使用 Playwright 进行端到端测试。Playwright 是 Cypress 和 Puppeteer 的继任者。它由 Microsoft 维护,是一个跨浏览器测试工具。它也比 Cypress 快得多。
在此处详细了解 Playwright 此处。
安装 Playwright 依赖项
Playwright 需要安装一些依赖项才能在本地运行。您可以通过运行以下命令来安装它们
npx playwright install --with-deps
配置 Playwright
测试位于 playwright/e2e
目录中。随意更改目录结构,只要您喜欢。如果这样做,请不要忘记在 playwright.config.ts
文件中更新测试位置。
export default defineConfig({
testDir: './playwright/e2e', // <-- Update this
您不需要在运行测试之前启动开发服务器。
Playwright 会为您启动开发服务器。它在 playwright.config.ts
文件的最底部进行配置
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url
:
'https://127.0.0.1:3000',
timeout
:
1 * 60 * 1000,
reuseExistingServer
:
!process.env.CI
}
运行测试
GitHub Actions 上的 Playwright
每次您向 main
分支提交拉取请求时,测试将在 GitHub Actions 上运行。
本地 Playwright
您可以通过运行以下命令在本地运行测试
npm run int
报告将转到 reports/e2e
目录。
Storybook
我们使用 Storybook V7 与 Webpack 5。Remix 在 Storybook 支持方面还略有落后,因此我们不得不做一些事情才能使其正常工作。
警告 Storybook 7 对 Storybook 的工作方式做出了一些根本性的改变。强烈建议您阅读 迁移指南 以了解发生了哪些变化。您习惯使用的东西可能不再以相同的方式工作。
在 Remix 社区中存在一个关于如何最好地解决此问题的 正在进行的讨论。
此代码不包含 remixStub,但可能会很快更改。
如果您知道如何正确配置它,请打开一个 PR。
运行 Storybook
您可以通过运行以下命令来运行 Storybook
npm run storybook
如果您正在寻找有关如何组织故事的灵感,您可以查看 Telekom Scale 项目
发布 Storybook
还记得我们之前设置 页面 吗?
当您推送到 main
分支时,Storybook 会自动发布到 GitHub Pages。
这是通过 .github/workflows/storybook.yml
工作流完成的。
访问已发布的 Storybook
在本自述文件的最顶部,您可以看到一个指向已发布的 Storybook 的徽章。
样式 / CSS
我们在这个项目中使用常规样式表,这意味着 共享组件样式 和 显式样式 的组合。
共享组件样式
共享组件样式位于 src/styles
目录中。它们被导入到使用它们的路由中。
// src/root.tsx
import styles from './styles/app.css';
export const links: LinksFunction = () => {
return [
{ rel: 'stylesheet', href: styles }
];
};
在整个应用程序中统一的样式从 src/root.tsx
文件加载,而特定于单个路由的样式从路由本身加载。
这些都是累加的,因此您可以拥有一个通过 root.tsx
加载到每个路由上的单个样式表,以及在特定路由上加载的附加样式表。
如果您需要组件特定的样式表,可以使用 显式样式 方法。
显式样式
为了让每个组件都有本地样式,我们使用的是 显式样式。
由于这些不是路由,因此不与 URL 段关联,Remix 无法知道何时预取、加载或卸载样式。我们需要将链接“显式”到使用这些组件的路由。
此解决方案稍微复杂一些,但它允许我们仅在加载组件时加载样式。
以 Hello
组件为例
import { useTranslation } from 'react-i18next';
import styles from './hello.css';
export const links = () => [
{ rel: 'stylesheet', href: styles }
];
export const Hello = () => {
const { t } = useTranslation();
return (
<h1 data-testid={'greeting'} className={'hello'}>{t('microcopy.helloWorld')}</h1>
);
};
export default Hello;
请注意它导入 hello.css
文件。该文件位于与组件相同的目录中。它还具有一个 links
导出,它返回样式表链接。
然而,在 Remix 术语中,组件不是路由,因此我们需要将链接“显式”到使用这些组件的路由。您可以在 src/routes/_index.tsx
文件中看到一个示例
import { Hello, links as helloLinks } from '~/components/Hello';
export const links: LinksFunction = () => ([
...helloLinks()
]);
我们从 Hello
组件导入 links
导出并将其添加到 _index.tsx
路由的 links
导出中。
是的,这比它应该的要复杂,但随着 Remix 的快速发展,我们希望这将在未来得到简化。
PostCSS
我们使用 PostCSS 处理 CSS。Remix 有一个内置的 PostCSS 插件,允许您直接将 CSS 文件导入到您的组件中。详细了解 Remix 中的 CSS 的工作原理。
我们的 PostCSS 配置位于 postcss.config.js
文件中,它在 Remix 构建应用程序的每次时候都会应用。这意味着您不必担心前缀或其他浏览器特定的 CSS 功能。只需编写您的 CSS,PostCSS 会自动处理其余工作。
Typescript 路径
我们使用 Typescript 路径。这意味着,我们可以使用方便的别名,而不是导入中的混乱相对路径。
我们默认定义了以下路径
~
-src
文件夹@styles
-src/styles
文件夹@test
-test
文件夹
这意味着,无论你在文件树的哪个位置,都可以使用 `~` 别名引用 `src` 文件夹。
import Hello from '~/components/Hello';
import appStyles from '@styles/app.css';
import { renderWithi18n } from '@test';
请随意在 `tsconfig.json` 文件中添加你自己的路径。
你可能想要添加的常用路径有:
@components
- `src/components` 文件夹@routes
- `src/routes` 文件夹@hooks
- `src/hooks` 文件夹
我们选择不添加这些路径,因为 `~/hooks` 和 `@hooks` 的区别不大,不值得添加额外的设置。
TypeScript 路径问题
不幸的是,TypeScript 路径有点深奥,不同工具之间的支持可能参差不齐。
Vitest
例如,Vitest 需要特殊的配置才能处理它。你可以在 `vitest.config.ts` 文件中找到配置。它既需要 vite-tsconfig-paths 插件,在某些情况下,你还需要手动将路径添加到 `resolve.alias` 数组中。
// vite.config.ts
resolve: {
alias: {
'~'
:
path.resolve(__dirname, './src')
}
}
Storybook
Storybook 也需要被告知要尊重 TypeScript 路径。我们使用 tsconfig-paths-webpack-plugin 来告诉 Storybook Webpack 配置要尊重这些路径。
我们将其添加到 `webpackFinal` 函数中,该函数位于 `.storybook/main.ts` 文件中。
webpackFinal: async config => {
config.plugins?.push(new DefinePlugin({
__DEV__: process.env.NODE_ENV !== 'production'
}));
if (config.resolve) {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(new TsconfigPathsPlugin()); // <--- this line
}
return config;
}
单元测试
我们使用 Vitest 作为单元测试框架。如果你不熟悉 Vitest,不用担心,它的界面与 Jest 非常相似,你不会有任何问题上手。
Vitest 的主要配置文件位于 `vitest.config.ts`。
这里有很多经过深思熟虑的决策,所以让我们逐一介绍。
全局变量:true
默认情况下全局变量是关闭的,但是为了让 `js-dom` 与 Vitest 一起工作,它们需要开启。
测试报告器
我们使用不同的报告器,具体取决于环境。在 CI 环境中,我们输出 `junit` 和 `cobertura` 报告,然后将这些报告发布到 GitHub Actions 摘要或拉取请求评论中。在你的本地机器上,我们使用 `html` 报告器来进行覆盖率分析,并使用默认的文本报告器来显示测试结果。
在这两种情况下,我们还打印出覆盖率报告的文本表示。
所有测试报告都位于 `reports` 目录中。
设置文件
如果你仔细观察,你会发现我们有一个 `setupFiles` 部分,它调用 `vitest.setup.ts` 文件。这个文件负责为测试设置环境。它安装 `@testing-library/jest-dom` 包,并设置一个通用的 `afterEach` 钩子来清理测试后的环境。
这可能不是每个人的喜好,所以请随意更改它。请记住,如果你删除全局 `afterEach` 钩子,你需要自己清理测试后的环境,所以确保运行 `npm run ci` 并查看什么坏了。
由于 Remix 依赖于浏览器 API(如 fetch),而这些 API 在 Node.js 中不可用,你可能会发现,在使用某些工具运行时,你的单元测试在没有这些全局变量的情况下会失败。
如果你需要添加更多全局变量,你可以在 `vitest.setup.ts` 文件中添加它们。
只需添加
import { installGlobals } from '@remix-run/node';
// This installs globals such as "fetch", "Response", "Request" and "Headers".
installGlobals();
请阅读更多相关信息 这里;
线程
虽然线程的承诺听起来很有吸引力,但开启线程会大幅降低 Vitest 的速度。这是一个已知问题,我们正在等待它得到修复。
覆盖率
该代码库附带 100%+ 的覆盖率,以覆盖边缘情况。我们知道这并非每个人的喜好,因此,如果你想删除 `statements`、`branches`、`lines` 和 `functions` 部分,可以从 `coverage` 配置对象中移除它们。
或者,你也可以修改 `package.json` 文件中的 `report` 脚本,以删除 `--coverage` 标志。
代码库本身的开发
注意
关于锁定文件的说明。
由于这是一个 "create" 包,所以没有包含锁定文件。这是为了确保在创建新项目时使用依赖项的最新版本。