警告 這個技術棧目前僅支援 TypeScript 和 NPM。
NPM 的要求來自於 GitHub Actions 腳本。我很快會讓它支援 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 設定
- 透過 GitHub Actions 使用 CDK 進行 AWS 部署
- 使用 AWS Lambda + Api Gateway + Cloud Front 進行正式環境建置
- 使用 AWS Lambda + Api Gateway 進行臨時建置(用於功能分支、Pull Request 等)
- 使用 Renovate 自動更新依賴套件
使用此技術棧
使用此技術棧建立您的專案
npx create-remix@latest --template meza/trance-stack my-app
設定過程會要求您輸入 GitHub 儲存庫名稱。如果您沒有儲存庫,別擔心,您可以在設定過程後建立。
警告
從現在開始,請在您自己的專案目錄中閱讀此文件。它將包含與您相關的連結,因為初始化腳本會將此 README 中的連結替換為針對您的專案自訂的連結。
現在啟動開發伺服器
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
變數用於加密 Session Cookie。它應該是一個長而隨機的字串。
GitHub 設定
注意 此專案使用 GitHub Actions。如果您不熟悉 GitHub Actions,可以在這裡閱讀更多相關資訊。
您需要執行一些操作,以確保 GitHub Actions 可以與您的專案一起運作。
工作流程權限
首先,前往https://github.com/meza/trance-stack/settings/actions,在 Workflow permissions
部分下,請確保它已設定為 Read and write permissions
選項。
如果沒有此權限,部署指令稿將無法建立必要的 GitHub 發布版本。
分支保護
接下來,前往https://github.com/meza/trance-stack/settings/branches並新增一些分支保護規則。
- main
- alpha
- beta
這些是將用於應用程式不同階段的分支。您可以根據自己的喜好設定這些分支的設定,但有一個設定需要確保取消勾選:Allow deletions
。
我們稍後將在部署章節中使用此設定,以防止已命名的環境被刪除。
頁面
接下來,前往 https://github.com/meza/trance-stack/settings/pages,並確認 Source
設定為 GitHub Actions
。這將允許我們將專案的 Storybook 部署到 GitHub Pages。
環境
注意 我們使用 GitHub 環境來管理應用程式的不同階段。您可以在這裡閱讀更多相關資訊。
GitHub 環境非常適合用來控制您的工作流程中使用的環境變數。
目前,請前往 https://github.com/meza/trance-stack/settings/environments 並建立以下環境
正式環境
Staging(預備環境)
Ephemeral(臨時環境)
這些環境在 部署工作流程中,例如使用 environment
鍵值來引用。Ephemeral
環境用於功能分支和 Pull Request,並在 臨時工作流程中引用。
變數 vs. 機密
有些設定值是敏感的,而有些則不是。例如,COOKIEYES_TOKEN
不敏感,但 AUTH0_CLIENT_SECRET
是。這主要來自於一些值會被嵌入到應用程式的 HTML 中,並對所有人可見。
警告 請仔細檢查服務的文件,以確保您正確設定它們。
如果您將機密設定為變數,或將變數設定為機密,應用程式將無法正常運作。
GitHub Token - 先做這件事!
為了讓發布正常運作,您需要建立個人存取權杖。它需要以下設定
- Expiration(到期時間):never(永不)
- Scopes(權限範圍)
- repo 用於私有儲存庫
- public_repo 用於公開儲存庫
建立權杖後,前往機密設定,並將其以 GH_TOKEN
加入。
持續部署設定
部署流程在 部署 章節中描述,但為了讓您開始,請建立 環境變數 章節中定義的環境變數和機密。
使用 Auth0 進行身份驗證
我們使用 Auth0 進行身份驗證。您需要建立一個 Auth0 帳戶並設定一個應用程式。
在建立新應用程式時,請確保設定以下設定
- 應用程式類型應為
Regular Web Applications
(常規 Web 應用程式) - 忽略快速入門部分
- 前往 Settings (設定) 並複製
Domain
(網域)、Client ID
(客戶端 ID) 和Client Secret
(客戶端密鑰) 並將它們貼到.env
檔案中 - 將 Token Endpoint Authentication Method (權杖端點身份驗證方法) 設定為
Post
- 前往
Allowed Callback URLs
(允許的回調 URL) 部分,並加入https://127.0.0.1:3000/auth/callback
- 前往
Allowed Logout URLs
(允許的登出 URL) 部分,並加入https://127.0.0.1:3000
- 前往
Allowed Web Origins
(允許的 Web 來源) 部分,並加入https://127.0.0.1:3000
- 前往
Allowed Origins (CORS)
(允許的來源 (CORS)) 部分,並加入https://127.0.0.1:3000
- 前往
Refresh Token Rotation
(刷新權杖輪換) 部分並啟用它,同時您還必須啟用Absolute Expiration
(絕對到期) 選項。
將 Auth0 變數新增至 GitHub
現在您有了 Auth0 變數,您需要將它們新增至您上面建立的 GitHub 環境中。
前往機密設定,並使用與 .env
檔案中變數相同的名稱新增 Auth0 機密。
如果您願意,可以為每個環境設定自訂值。例如,您可以將 AUTH0_DOMAIN
設定為 dev-123456.eu.auth0.com
給 Staging
環境,而將 prod-123456.eu.auth0.com
給 Production
環境。
但為了簡單起見,您只需在主要的 Actions 機密頁面上設定相同的值一次,它將用於所有環境。
為功能分支/PR 部署啟用 Auth0 整合
如果您想為功能分支/PR 部署啟用 Auth0 整合,您需要執行一些額外的步驟。由於功能分支/PR 部署是臨時性的,它們每次部署都會有不同的網域名稱。這表示您需要將網域名稱新增至 Allowed Callback URLs
和 Allowed Logout URLs
中。
為了讓這個過程無痛,我們可以在網域名稱中使用 *
通配符。這將允許使用任何網域名稱。
在上述初始設定期間,您在幾個地方新增了 https://127.0.0.1:3000
。您需要將 ,https://*.execute-api.us-east-1.amazonaws.com
加入相同的地方。(注意開頭的逗號。網域需要用逗號分隔)
注意 您需要將
us-east-1
部分替換為您正在使用的區域。
例如,Allowed Callback URLs 部分應如下所示
https://127.0.0.1:3000/auth/callback,https://*.execute-api.us-east-1.amazonaws.com/auth/callback
警告
*
通配符將允許您使用您想要的任何網域名稱。然而,這樣做的代價是安全性。我們強烈建議您在 Auth0 上為您的功能分支/PR 部署建立一個替代租戶。
從應用程式中移除 Auth0 整合
- 從
.env
檔案和 GitHub 機密中刪除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 帳戶並設定一個資源。
當您完成資源設定後,您需要複製資料串流的 Measurement ID
(評估 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 ... />
元件及其 import。 - 執行
vitest --run --update
來更新快照。
Hotjar 整合
我們使用 Hotjar 進行熱圖和使用者錄製。您需要建立一個 Hotjar 帳戶並設定一個新網站。
當您設定好網站後,前往 https://insights.hotjar.com/site/list 並複製您網站的 ID,並將 HOTJAR_ID
變數貼到 .env
檔案中。
您還必須前往 變數設定,並新增與 .env
檔案中相同的變數名稱。
警告
HOTJAR_ID
是為 actions 設定為變數。
從應用程式中移除 Hotjar 整合
- 從
.env
檔案和 GitHub 變數中刪除HOTJAR_ID
變數。 - 刪除
src/components/Hotjar
目錄。 - 從
src/types/global.d.ts
檔案中的appConfig
類型中刪除相關類型。 - 從
src/root.tsx
檔案中刪除<Hotjar ... />
元件及其 import。 - 執行
vitest --run --update
來更新快照。
PostHog 整合
我們使用 PostHog 進行分析。您需要建立一個 PostHog 帳戶並設定一個新專案。
當您設定好專案後,前往 https://posthog.com/project/settings 並複製您專案的 API 金鑰,並將 POSTHOG_TOKEN
變數貼到 .env
檔案中。您還需要根據您的資料駐留偏好將 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 ... />
元件及其 import。 - 執行
vitest --run --update
來更新快照。 - 從
package.json
檔案中刪除posthog
相依性。 - 按照編譯和測試錯誤來移除所有使用
posthog
相依性的程式碼。
Renovate 機器人設定
我們使用 Renovate 來管理相依性更新。若要使用它,您需要安裝 Renovate GitHub App。
首先,前往 https://github.com/apps/renovate 並點擊 Install (安裝) 按鈕。
在下一個畫面中,我們建議您選擇「All repositories」(所有儲存庫) 以方便使用,但您可以將其設定為僅在您目前所在的儲存庫中運作。
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
- 組織 slugSENTRY_PROJECT
- 專案 slug
我們將使用這些來將原始碼對應檔傳送到 Sentry,以便將錯誤正確對應到原始碼。
部署腳本會自動將原始碼對應檔上傳到 Sentry,然後在本機將其移除,這樣它們就不會上傳到環境中。
如何找到 DSN
首先,前往專案設定
然後在側邊欄中,點擊 Client Keys (DSN)
最後,複製 DSN
值
從應用程式中移除 Sentry 整合
- 從
.env
檔案和 GitHub 變數中刪除SENTRY_DSN
變數。 - 執行
npm remove @sentry/*
以移除所有 Sentry 套件。 - 從
appConfig
中移除sentryDsn
,並從src/types/global.d.ts
檔案中的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 發布外掛程式。
若要有效使用 Conventional Commits,您需要了解以下基本原則
您的 commit 訊息決定是否將新的部署發布到生產環境。
觸發建置的訊息為
fix: ...
- 修正錯誤feat: ...
- 新增新功能
不會觸發新版本(因此不會建置)的訊息為
docs: ...
- 變更文件chore: ...
- 變更建置流程或輔助工具和程式庫,例如文件產生refactor: ...
- 既不修正錯誤也不新增功能的程式碼變更style: ...
- 不影響程式碼意義的變更(空白、格式、缺少分號等)test: ...
- 新增遺失的測試或修正現有測試ci: ...
- 變更 CI 設定檔和腳本perf: ...
- 改善效能的程式碼變更
使用語義版本控制的分支策略
我們將在 部署 章節中討論部署如何運作。現在,我們先來看看分支策略如何與版本控制搭配運作。
有 3 個主要分支
main
- 這是主要分支。這是部署到生產環境的分支。beta
- 這是部署到 beta(預備)環境的分支。alpha
- 這是部署到 alpha(預備)環境的分支。
當您推送至 main
分支時,會將新版本發布到生產環境。版本由 commit 訊息決定,而推送至 main
分支的每個 commit 都會觸發新版本。
當您推送至 alpha
或 beta
分支時,會建立新的 預先發布 版本。這可讓您反覆處理即將發布的功能,而無需擔心每次推送引入新功能或修正的 commit 時都會增加版本號碼。
例如,如果您在生產環境中有 1.0.0
版本,並且將 commit 推送至 alpha
分支,則版本將為 1.1.0-alpha.0
。如果您將另一個 commit 推送至 alpha
分支,則版本將為 1.1.0-alpha.1
,依此類推。
當您將來自 alpha
或 beta
分支的提取請求合併至 main
分支時,這些分支中的所有變更都會被收集並捆綁到單一版本中。若要遵循上述範例,如果您在生產環境中有 1.0.0
版本,並合併具有 1.1.0-alpha.1
版本的 alpha
分支,則您在生產環境中新建立的版本將為 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 來檢查 commit 訊息。設定位於 package.json
檔案中。每當您進行 commit 時都會進行程式碼檢查。如果 commit 訊息不符合 Conventional Commits 格式,commit 將會失敗。
程式碼檢查本身是由 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
區段
執行階段相依性是我們用來執行應用程式的程式庫。這也表示安全性與錯誤修正對於這些相依性非常重要。
我們希望盡快更新這些相依性,因此我們有以下設定
次要版本和修補程式版本
- 建立一個在 commit 訊息中具有fix:
前置詞的提取請求,並在可能的情況下自動合併主要版本
- 建立一個在 commit 訊息中具有fix:
前置詞的提取請求,並且「不」自動合併
開發相依性
開發相依性是
package.json
檔案中的devDependencies
區段
開發相依性是我們用來開發應用程式的程式庫。這表示當我們更新這些相依性時,我們不需要發布應用程式的新版本。
我們仍然希望盡快更新這些相依性,因此我們有以下設定
次要版本和修補程式版本
- 建立一個在 commit 訊息中具有chore:
前置詞的提取請求,並在可能的情況下自動合併主要版本
- 建立一個在 commit 訊息中具有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' }}
此處的 Production
和 Staging
字詞直接參考我們已設定的 GitHub 環境。
警告 這表示
alpha
和beta
分支都會被部署到Staging
環境。
這樣做是為了堆疊的方便性,但我們強烈建議您根據自身需求更改此設定。或許可以新增一個獨立的 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 Cloud Development Kit (CDK) 是一個開源軟體開發框架,用於以程式碼定義雲端基礎架構,並透過 AWS CloudFormation 佈建。
注意 如果您想了解我們為什麼選擇 CDK,請查看相關的 ADR。
大多數基礎架構都定義在 deployment
目錄中。 deployment/lib
目錄包含用於建構基礎架構的自訂 Constructs。
環境變數
為了部署應用程式,您需要設定以下環境變數
變數 | 密鑰 | 描述 |
---|---|---|
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 目錄
deployment/stacks
目錄包含實際部署到 AWS 的堆疊。它們的命名應該是不言自明的。我們有一個用於 Ephemeral
環境,另一個用於 Production
環境。
如果您檢查任一部署檔案,您會注意到部署基本上是一個單一命令
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/Pull Request。
它之所以比它需要來得複雜一些,是因為我們不想每次部署相同分支時都發布 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
腳本中的Create 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
元件。只有在啟用 AUTH
功能時,才會呈現 Login
元件。
區分環境
在 PostHog 中,您的主要單位稱為「組織」。一個組織可以擁有多個「專案」,這些專案本質上是環境。例如,您可以擁有一個 production
專案和一個 staging
專案。
這允許您為每個環境擁有不同的功能標誌、使用者和資料。請隨意為每個環境建立一個新專案,然後設定適當的環境變數。
I18N - 國際化
我們正在使用 i18next 進行國際化。您可以在 i18next 文件中閱讀更多相關資訊。為了將其與 Remix 整合,我們正在使用 remix-i18next 套件,而我們的設定是基於 remix-i18next Readme 檔案。
您可以在 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
hook。
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 Readme 檔案中找到一些很棒的提示。
Lefthook
提交驗證和自動依賴項安裝是由 Lefthook 完成的
設定檔位於 .lefthook.yml
。您可以看到所有發生的命令以及它們所附加的 git hooks。
如果每次提交都執行所有測試太多,您可以隨時將其設定為在 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
分支開啟 pull request 時,測試都會在 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
如果您正在尋找如何組織您的 stories 的靈感,您可以查看 Telekom Scale 專案
發佈 Storybook
還記得我們在開始時設定的Pages嗎?
當您推送到 main
分支時,Storybook 會自動發佈到 GitHub Pages。
這是透過 .github/workflows/storybook.yml
工作流程完成的。
存取已發佈的 Storybook
在此 README 的最上方,您可以看到一個連結到已發佈 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 設定要尊重路徑。
我們將其新增至 .storybook/main.ts
檔案中的 webpackFinal
函式。
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 摘要或作為 Pull Request 的評論。在您的本機上,我們會使用 html
報告器來產生覆蓋率報告,並使用預設的文字報告器來顯示測試結果。
在這兩種情況下,我們也會印出覆蓋率報告的文字表示。
所有的測試報告都會存放在 reports
目錄中。
設定檔案
如果您仔細觀察,您會發現我們有一個 setupFiles
區段,它會呼叫 vitest.setup.ts
檔案。這個檔案負責設定測試環境。它會安裝 @testing-library/jest-dom
套件,並設定一個通用的 afterEach
hook,以便在測試後進行清理。
這可能不是每個人都喜歡的方式,所以您可以隨意更改。請記住,如果您移除全域的 afterEach
hook,您將需要自行在測試後進行清理,因此請務必執行 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%+ 的覆蓋率,以涵蓋邊緣案例。我們知道這不是每個人的偏好,因此如果您想,您可以從 coverage
設定物件中移除 statements
、branches
、lines
和 functions
區段。
或者,您可以修改 package.json
檔案中的 report
指令稿,以移除 --coverage
標誌。
堆疊本身的開發
注意
關於鎖定檔的說明。
由於這是一個 "create" 套件,因此不包含鎖定檔。這是為了確保在建立新專案時使用最新版本的相依性。