iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
Modern Web

用 Effect 實現產品級軟體系列 第 5

[學習 Effect Day5] 建立 Effect 專案 (一)

  • 分享至 

  • xImage
  •  

前言

我過去經驗主要是從使用者視角來打造我心中想要的產品。對於如何把產品(尤其是後端服務)做到足夠穩健(robust)並不是我的強項。所以我選擇直接用官方 CLI 來開始建構一個專案。觀察 Effect 團隊預設的「起手式」是什麼?有用什麼樣的套件?那讓我們開始吧!但如果你只是想來學習 Effect 的話,也可以直接跳過這一個章節,直接從下下一篇開始。

Create Effect App CLI

我偏好用 pnpm 建立專案:

# pnpm
pnpm create effect-app@latest

畫面會出現互動式精靈,依序詢問下列選項;我把自己的選擇附在下面:

✔ What is your project named? … effect-app
✔ What type of project would you like to create? …  Template
✔ What project template should be used? …  Basic
✔ Initialize project with Changesets? … on 
✔ Initialize project with a Nix flake? … on 
✔ Initialize project with ESLint? … on 
✔ Initialize project with Effect's recommended GitHub actions? … on 

切到專案資料夾並安裝依賴

cd effect-app
pnpm install

執行專案程式碼,確認可以正常執行

pnpm tsx ./src/Program.ts
# 輸出:
# timestamp=2025-09-10T17:39:52.827Z level=INFO fiber=#0 message="Hello, World!"

如果都成功的話,建議先初始化 git repo (git init),Effect CLI 沒有幫你處理這一個部分。然後我們就來看一下,Effect CLI 幫我們做了哪些事情。

ESLint 我就不多說啦~基本上網站開發必備。如果有用網站開發框架,例如 Next.js、Vite 等,通常會內建安裝 ESLint。我這裡假設大家都很熟悉,所以我們就只講 GitHub ActionsChangesetsNix flake 這三個也許對 Junior 的工程師較陌生的選項。

GitHub Actions

GitHub Actions 是 GitHub 提供的事件驅動自動化服務(CI/CD 與各式工作流)。你可以在 .github/workflows/*.yml 定義「工作流程(workflow)」:
它可用來自動執行型別檢查、Lint、測試、建置、建立版本 PR、發佈到 npm、產生快照套件、部署到雲端等,讓重複性流程標準化並在 PR/主線上持續把關品質。

GitHub Actions

.github/workflows/ 裡放的是自動化流程的設定檔(YAML)。下面就用目前專案裡的三個檔案,逐一解釋它們在做什麼:

check.yml(品質驗證 CI)

name: Check

on:
  workflow_dispatch:
  pull_request:
    branches: [main]
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions: {}

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - run: pnpm codegen
      - name: Check source state
        run: git add src && git diff-index --cached HEAD --exit-code src

  types:
    name: Types
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - run: pnpm check

  lint:
    name: Lint
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - run: pnpm lint

  test:
    name: Test 
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - run: pnpm test

  • 觸發:workflow_dispatch、對 mainpull_requestpush
  • 併發:相同分支的舊任務會被取消(concurrency),避免重複浪費資源
  • 作業與重點步驟:
    • Build:pnpm codegen 後用 git diff-index --cached HEAD --exit-code src 檢查產物是否乾淨,避免忘記提交產生碼
    • Types:pnpm check 做型別檢查
    • Lint:pnpm lint 做語法/風格檢查
    • Test:pnpm test 執行測試
  • 共用安裝:透過 ./.github/actions/setup 安裝環境與依賴

release.yml(正式發佈)

name: Release

on:
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}

permissions: {}

jobs:
  release:
    if: github.repository_owner == 'Effect-Ts'
    name: Release
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write
      id-token: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - name: Create Release Pull Request or Publish
        id: changesets
        uses: changesets/action@v1
        with:
          version: pnpm changeset-version
          publish: pnpm changeset-publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

  • 觸發:pushmain
  • 條件:if: github.repository_owner == 'Effect-Ts'(目前只在 Effect-Ts 擁有的 repo 才會執行)
    • 若要在你的 repository 生效,請把條件改成你的帳號(例如 EricTsai83, 這是我的帳號,請別用這
      個😌),或直接移除此行。
  • 權限(permissions):設定內容(contents)、PR (pull-requests) 和 OIDC簽章(id-token)皆開啟寫入權限
    • 補充:OIDC 簽章是透過 OpenID Connect 發給工作流程的「短期身分的 token」,讓外部服務(如 AWS/GCP/Azure、npm)驗證這次操作確實來自此 GitHub 專案、指定 workflow 或 commit,降低長期金鑰外洩風險。
  • 主要步驟:
    • 安裝環境:使用 ./.github/actions/setup
    • Changesets 發佈:changesets/action@v1
      • version 指令:pnpm changeset-version
      • publish 指令:pnpm changeset-publish
      • 需要 Secrets:GITHUB_TOKEN(自帶)與 NPM_TOKEN(你需在 Repo Settings → Secrets 設定)
  • 行為:有 changeset 時會建立「版本 PR」;版本 PR 合併到 main 後會 bump 版本、產生 changelog 並嘗試發佈到 npm

snapshot.yml(PR 快照設定)

name: Snapshot

on:
  pull_request:
    branches: [main, next-minor, next-major]
  workflow_dispatch:

permissions: {}

jobs:
  snapshot:
    name: Snapshot
    if: github.repository_owner == 'Effect-Ts'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        uses: ./.github/actions/setup
      - name: Build package
        run: pnpm build
      - name: Create snapshot
        id: snapshot
        run: pnpx pkg-pr-new@0.0.24 publish --pnpm --comment=off

  • 觸發:對 mainnext-minornext-major 的 PR;或手動 workflow_dispatch
  • 條件:同樣限制 if: github.repository_owner == 'Effect-Ts'
    • 要在你的 repository 生效,請換成自己的帳號或移除條件
  • 主要步驟:
    • Build:pnpm build
    • 建立快照:pnpx pkg-pr-new@0.0.24 publish --pnpm --comment=off
  • 作用:在 PR 上建立「不佔正式版本號的預覽套件」,方便審查者安裝試用(通常會在 PR 留下可安裝的套件資訊)

以上三個 Workflow 搭配 Changesets 設定,可以把「品質檢查 → 版本 PR → 合併後發佈 → PR 快照測試」串成一條穩健的產品級發佈管線。若你要在自己的帳號下啟用發佈與快照,請務必在 Actions secrets 新增 NPM_TOKEN。但如果你只是來學習 Effect 的話,就先跳過這一部分吧~我自己也不太確定30 篇的文章篇幅,最終文章會不會包含這一個部分。
但如果你想嘗試的話,步驟如下:

  1. 先參考這篇文章,產生 token
  2. 進入該 Repo 的主頁面
  3. 點選 Settings(右上角的齒輪 ⚙️)
  4. 在左側選單找到 Secrets and variables ➝ Actions
  5. 點選 New repository secret
  6. 在 Name 輸入 NPM_TOKEN,在 Value 貼上你從 NPM 產生的 Token
  7. 儲存

這樣 GitHub Actions Workflow 執行時,就可以透過 ${{ secrets.NPM_TOKEN }} 使用這個 Token。

Composite Action:

  • 位置:.github/actions/setup/action.yml
  • 用途:標準化 CI 的環境安裝與依賴安裝,避免在每個 workflow 重複撰寫步驟。
  • 內含步驟:
    • 安裝 pnpm(pnpm/action-setup@v3
    • 安裝 Node(actions/setup-node@v4),開啟 pnpm 快取
    • 安裝依賴:pnpm install
  • 參數:node-version(預設 20.16.0),可在使用時覆寫。

使用方式(可選覆寫 Node 版本):

- name: Install dependencies
  uses: ./.github/actions/setup
  with:
    node-version: 20.16.0

Changesets:管理版本與發佈流程的好幫手

它是什麼?

Changesets 是一個為 Monorepo 設計、同樣適用於單一專案的版本與發佈管理工具。每次你修改功能或修 bug,只要新增一個 changeset 檔(YAML/Markdown),把哪些套件有改變、變更等級(major/minor/patch),以及發佈後要寫進 changelog 的內容說清楚即可。

為什麼要用?

版本變動有跡可循,不用到處手動改 package.json;changelog 可以自動產生,方便團隊與社群追蹤;而且搭配 GitHub Actions,在 PR merge 後就能自動決定是否 bump 版本並發佈到 npm。

什麼時候開啟?

如果只是 demo 或 side project,關掉也無妨;但若你要維護可重用套件、發佈到 npm,或管理 Monorepo,建議開啟,幾乎是必備。

快速上手(安裝與初始化)

如果你是用 Effect CLI 建立專案,在互動精靈中勾選了 Changesets,會自動安裝並初始化。

初始化後會產生 .changeset/ 資料夾和 config.json 設定檔。一開始的設定檔如下:

{
  "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    {
      "repo": "<PLACEHOLDER>"
    }
  ],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

設定欄位說明

  • $schema: 指向一個 JSON Schema 的連結,它用於描述此json檔案應該是什麼資料格式。也可以讓 IDE 提供屬性提示和欄位驗證的功能,協助開發人員編寫和理解JSON 檔案。
  • changelog: 指定 changelog 產生器。此處使用 @changesets/changelog-github,需將 repo 改為實際的 owner/repo(目前為 <PLACEHOLDER>)。例如,"repo": "EricTsai83/effect-app"
  • commit: 若為false的情況,在執行 changeset version 時,Changesets 只會更新各 package.json 的版本號與 CHANGELOG.md,不會自動建立 Git commit。這代表你需要手動執行 git addgit commit 來保存變更。
  • fixed: 用來定義一組必須永遠用相同版本號釋出 的套件群組。單一套件或無需求可留空。
{
  "fixed": [
    ["@acme/ui", "@acme/docs"]
  ]
}
// 效果:
// 1. 只要 @acme/ui 改版 → @acme/docs 即使沒變動也會同步 bump 同一個版本。
// 2. 所以如果這次變更需要 @acme/ui 從 1.2.0 → 1.3.0,那麼 @acme/docs 也會被強制一起升到 1.3.0。
// 3. 這樣能保證這兩個套件的發佈版本號 永遠一致。
  • linked: linked 功能類似 fixed,但不同之處在於它會同步決定 bump 等級,但不強制相同版號。
{
  "linked": [
    ["@acme/ui", "@acme/docs"]
  ]
}
// 效果:
// 假設現在版本號是:A=1.5.0、B=2.3.1。如果 A 有改動需要 minor bump,B 也會 minor bump。結果就會變成 A=1.6.0、B=2.4.0
  • access: 用來控制發佈到 npm 上的「存取權限」。
    • public → 代表這個 package 會被發佈成 公開套件,任何人都可以從 npm 下載。特別是對 有 scope 的套件(例如 @mycompany/ui,其中 @mycompany 就是 scope),npm 預設會將 scoped package 視為私有,因此如果要讓它公開使用,必須額外指定為 public。
    • restricted → 代表這個 package 會被發佈成 私有套件,只有擁有權限的帳號或組織才能下載使用,常用於公司內部專案或私有元件庫。
  • baseBranch: 主要分支名稱(通常是 main)。當你在主要分支(baseBranch,例如 main)執行 changeset version,Changesets 會:
    • 比對目前 main 上的狀態
    • 找出已經新增但尚未發版的變更檔(changesets)
    • 計算出這些變更對各 package 的版本號影響
    • 最後更新 package.json 裡的版本號與 CHANGELOG.md
  • updateInternalDependencies: 它定義「當內部套件(internal dependency)更新版本時,相依它的套件至少要跟著升級什麼等級」。例如:
    • @mycompany/ui 依賴 @mycompany/utils
    • 如果 @mycompany/utils 發布了新版本,Changesets 就要決定:ui 要不要跟著 bump?要 bump 到哪個等級?("patch", "minor", "major")。既然講到這個,我們寫說明一下這三個等級的差別:
      • patch:錯誤修正 / bug 修復,但不影響現有功能,也不新增功能,只是修正現有功能的問題。(1.0.0 → 1.0.1)
      • minor:新增功能,但保持 Backward Compatible(向下相容)。可以是功能加強、API 新增、性能提升。(1.0.0 → 1.1.0)
      • major:任何會破壞既有 API 的修改,例如:刪除或重構 API、改掉核心設計、全面性大改。(1.0.0 → 2.0.0)
  • ignore: 從版本計算中忽略的套件清單(例如範例、測試用套件)。

常見工作流程(單一套件與 Monorepo 通用)

  1. 建立 changeset(在你的 feature/修補分支上)
pnpm changeset
# 互動式選擇有影響的套件、版本等級(major/minor/patch),並撰寫摘要
  1. 檢視目前將要發佈的變更與將產生的版本:
pnpm changeset status
  1. 合併到 main 後進行版本號與 changelog 生成:
pnpm changeset-version

補充:你可能一開始運行的時候會失敗,這是因為 CLI 初始化時,幫你建的 script 運行語法是 changeset version && node scripts/version.mjs,但他卻沒有給你能運行的 script。我這邊參考官方 GitHub 寫了一個版本,如下:

路徑: scripts/version.mjs

import * as Fs from "node:fs"
import Package from "../package.json" with { type: "json" }

const tpl = Fs.readFileSync("./scripts/version.template.txt").toString("utf8")

Fs.mkdirSync("src/internal", { recursive: true })
Fs.writeFileSync(
  "src/internal/version.ts",
  tpl.replace("VERSION", Package.version)
)

路徑: scripts/version.template.txt

/** @internal */
export const moduleVersion = "VERSION"

我們先前有講package.json 內的版號是靠 changeset 來變更。而我們可以利用讀取 package.json 內的版號,來生成 src/internal/version.ts 檔案。把 package.json 的版本在編譯時「寫進程式碼」成常數,執行時直接 import 使用,不需要讀檔或動態載入。讓程式內的版號可以與實際版號同步。方便在日誌、錯誤回報、HTTP 標頭/UA、CLI --version 中引用。進一步提升可觀測性。

這一步會根據 .changeset/*.md 決定每個套件的新版本,更新 package.jsonCHANGELOG.md

  1. 發佈到 npm(需要 NPM_TOKEN 並完成登入/權限設定):
pnpm changeset-publish

不過研究如何發佈 npm 套件不是本文的重點。所以就不多做介紹啦~😀

總結

本篇示範以 pnpm 建立並驗證 Effect 專案,並整理 GitHub Actions 與 Changesets 的關鍵設定與實務流程;下一篇將介紹 Nix flake 在此專案中的定位與作用。

參考


上一篇
[學習 Effect Day4] 為什麼需要 Effect
下一篇
[學習 Effect Day6] 建立 Effect 專案 (二)
系列文
用 Effect 實現產品級軟體10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言