环境与版本

  • platform => mac os
  • node => v22.2.0
  • npm => 10.8.0
  • pnpm => 9.1.3
  • yarn => 1.22.22
  • turbo => 1.13.3
  • changesets => 2.27.5

请注意文档时效和工具版本,浏览前建议查看官方文档,以获得最新信息!

前置准备

pnpm 使用 workspace
Node corepack 使用
pnpm 切换国内镜像源
Turorepo

本文档搭配 corepack 更佳~ 还需要掌握 pnpmworkspace 相关知识哦~

快速开始

命令初始化

pnpm dlx create-turbo@latest

使用模版

本文使用的模版:monorepo with changesets

pnpm dlx create-turbo -e with-changesets

目录结构

注意,无论生成还是模版package.json 都含有 packageManager 字段,请将你的 pnpm 的版本与其对齐,或者参考 Node corepack 使用

demo-turoborepo
├── apps
│   ├── docs
│   └── web
├── packages
│   ├── ui
│   ├── eslint-config
│   └── typescript-config
├── package.json
├── turbo.json
└── pnpm-workspace.yaml

Turborepo

本文主要在 changesets,所以此处简单介绍下~

Turborepo 介绍

Turborepo 是专为 JavaScriptTypescriptmonorepo 项目设计的高性能构建系统。可以高效管理和构建项目中多个 packages,通过缓存构建和测试结果,来提升开发和持续集成的效率。
Turborepo 旨在提高大型 monorepo 项目的构建效率,在复杂的项目中,可以更好的处理任务之间的依赖关系,并保证构建的正确性和效率。

优势

  • 多任务并行处理
  • 增量构建
  • 云缓存

Pipeline - 管道

Turborepo 为开发人员提供了一种以常规方式显示指定任务关系的方法。 在 Turborepo 中可以在项目根目录中定义 turbo.json 来配置输出、缓存依赖、打包等功能。

pipeline 中的每一个 key 都指向我们在 package.json 中定义的 script 脚本执行命令,并且在 pipeline 中的每一个 key 都可以被 turbo run 执行。

在执行 turbo run 命令时,turbo 会根据 pipeline 中的配置,对每个 package.json 中的 script 执行脚本进行有序的执行缓存输出的文件日志

turbo.json
{ "$schema": "https://turbo.build/schema.json", "globalDependencies": ["**/.env.*local"], "pipeline": { "build": { // `build` tasks being completed first // (the `^` symbol signifies `upstream`). "dependsOn": ["^build"], // note: output globs are relative to each package's `package.json` // (and not the monorepo root) "outputs": [".next/**", "!.next/cache/**"] }, "test": { // A package's `test` script depends on that package's // own `build` script being completed first. "dependsOn": ["build"], "outputs": [], // A package's `test` script should only be rerun when // either a `.tsx` or `.ts` file has changed in `src` or `test` folders. "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] }, "lint": { "dependsOn": ["^lint"] }, "dev": { // Setting cache to false is useful for daemon or long-running "watch" // or development mode tasks you don't want to cache. "cache": false, // Label a task as persistent if it is a long-running process // such as a dev server or --watch mode. "persistent": true } } }

dependsOn

常规依赖

如果任务需要依赖其他任务,则可以放入 dependsOn 内。例如执行 deploy 任务时需要 buildtestlint 任务先完成
当然,在这里你只需要执行 turbo run deploy 即可,turbo 会按 dependsOn 中定义脚本执行

demo-turoborepo/turobo.json
{ "turbo": { "pipeline": { "deploy": { "dependsOn": ["build", "test", "lint"] } } } }

依赖工作空间中的包 - From dependent workspaces

目录结构

例如当 docs build 时,需要 uihooks build 完成之后,此时需在 dependsOn 中配置 ^ 符号来明确依赖关系。

apps/
  docs/package.json # 依赖 ui 和 hooks
packages/
  ui/package.json
  hooks/package.json
turbo.json
package.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      // A workspace's `build` command depends on its dependencies'
      // and devDependencies' `build` commands being completed first
      "dependsOn": ["^build"]
    }
  }
}

拓扑依赖 - Dependencies outside of a task

稍微有点绕,可以先阅读官方文档,或者从 demo 中找到思路。
demo-turborepo/test-topo,在项目根目录运行 turbo run checkType 再修改 ui 项目中的 typescript 类型代码,再运行 typeCheck,最后可在 docs 项目中查看 ui 的类型正确与错误

目录结构

apps/
  docs/package.json # 依赖 ui
  web/package.json  # 依赖 ui
packages/
  ui/package.json   # 无依赖
turbo.json
package.json
turbo.json
{ "pipeline": { // fake task "topo": { "dependsOn": ["^topo"] }, "typeCheck": { "dependsOn": ["topo"] } } }
demo-turborepo/test-topo/docs/package.json
{ "name": "docs", "script": { "typeCheck": "tsc --checkJs --noEmit" } }

应用场景

先了解业务场景,再尝试理解这段逻辑。

场景:docsweb 项目依赖 ui 组件,ui 组件无其他负作用依赖,三个项目都由 typescript 构建,且都有自己的 typeCheck 任务。
先不考虑更复杂的任务执行,按正常 dependsOn 的思路配置可能只要 "dependsOn": ["^typeCheck"] 即可,也能完成 typeCheck 任务,正常缓存。
其实你可以发现,每个 typeCheck 任务都是可以独立执行的,不需要依赖其他 typeCheck 任务结果,上述配置虽然可以完成任务,但 Turbo 的优势就是多任务并行,所以此类场景可以考虑使用并行来优化速度。

turbo.json
{ "pipeline": { "topo": { "dependsOn": ["^topo"] }, "typeCheck": { "dependsOn": ["topo"] } } }

所以这段配置可以理解为,pipeline 中的 topo(topological)任务是个虚拟任务(当然你可以随意命名),不存在于任何 package.json 的脚本中,Turborepo 会“立即”完成该任务,然后会并行执行依赖于它的 typeCheck 任务。
虽然不是真实存在任务,但 turbo 也会创建 webuidocsui 之间的依赖关系,所以当 ui 发生变化时,webdocs 的缓存也会失效。

pipeline 中的其他字段可以查看官方文档(pipeline),这里展开 dependsOn 是因为文档中的这一段描述比较绕,需要结合实际场景理解。。。

本地远程缓存

Turbo 不仅支持在本地缓存结果,也支持多人开发或在 CI 中使用的远程缓存模式。

登录 Vercel

执行命令后,你将跳转至 Vercel 的授权页面

turbo login
# or
pnpm dlx turbo login

开启缓存

开启后执行 turbo run 命令的缓存将同时在本地和远程保存

turbo link
# or
pnpm dlx turbo link

测试远程缓存

rm -rf ./node_modules/.cache/turbo

删除缓存后再执行 turbo run build,若显示 Remote caching enabled,此时 turbo 将在远程下载日志和产物,并在你执行时重新展示。

关闭缓存

turbo unlink
# or
pnpm dlx turbo unlink

在 CI 中使用远程缓存 - Using Turborepo with GitHub Actions

Turbo 支持在 CI 中配置远程缓存,如 GitHub Actions。在 actions 中,turbo 支持多种缓存,如使用 vercel 的远程缓存或 actions 提供的 actions/cache,这里只介绍 turbo 的了~

设置环境变量

在你的 .github/workflows/[release].yml 部署文件中添加环境变量: TURBO_TOKEN TURBO_TEAMturbo 会识别并使用远程缓存

.github/workflows/[release].yml
name: Release on: push: branches: - "main" jobs: release: name: Release runs-on: ubuntu-latest # To use Turborepo Remote Caching, set the following environment variables for the job. env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Install Dependencies run: pnpm i - name: Build run: pnpm build

生成 secrets TURBO_TOKEN

注意,请设置 secrets
生成路径 vercel => Account Settings => Tokens => Create,生成后在 repository => settings => Secrets and variables => Repository secrets => create TURBO_TOKEN 即可

生成 variables TURBO_TEAM

注意,请设置 variables
同上面账户设置,Account Settings => General => Default Team 复制 Team ID 在 actions repository => settings => Secrets and variables => Repository variables => create TURBO_TEAM 即可

缓存效果

在测试项目中查看 Create Release Pull Request or Publish to npm
图片中可以看出 CI 和本地的缓存 hash 一致。

changesets

changesets 是用于管理版本和生成变更日志的工具,专注多包管理。相较于 lerna 客制化更高,配置灵活,在 CI 中更友好。

安装 changeset

monorepo 项目中添加依赖。第二个包为 changelog 风格,后续会介绍。

pnpm add -Dw @changesets/cli
# or
pnpm add -Dw @changesets/cli @changesets/changelog-github
# or
pnpm add -Dw @changesets/cli @changesets/changelog-git

初始化

pnpm changeset init 

执行后会在项目根目录生成 .changeset 文件夹,内含 README.mdconfig.json 文件

.changeset/config.json
{ "$schema": "https://unpkg.com/@changesets/config@3.0.1/schema.json", "changelog": [ "@changesets/changelog-github", { "repo": "tardis-ksh/demo-turborepo" } ], "commit": false, "fixed": [], "linked": [], "access": "public", "updateInternalDependencies": "patch", "ignore": ["@tardis-ksh/docs", "@test/**"], "baseBranch": "main" }

配置说明

* baseBranch(git branch name)

baseBranchchangesets 用于比较变更的分支,通常为你主要分支,如 mainmaster 等。
改值较生成默认 main,请注意修改。

access (restricted | public)

包的发布方式,public 为公开,restricted 为私有。你可以在包的 package.jsonpublishConfig.access 字段来覆盖。
如果你不想某个包被发布,在该包的 package.json 中设置 "private": true 即可。

commit (boolean, or module path as a string, or a tuple like [modulePath: string, options: any])

在每次执行 changeset addchangeset version 后是否自动提交这些更改。该值接受 Boolean 或自定义行为。
这里介绍下 Boolean 时的场景:

  • true - 每次执行 changeset addchangeset version 后自动提交变更的文件,在 changeset add 时生成的文件 changeset 变更文件在 .changeset 文件夹中,若为 true 时只会提交这些变更文件且也会将此次 commit id 作为 changelog 内容记录 -> 517c63d
  • false - 手动提交变更文件。changeset 会将包含变更文件的这次 commit 作为 changelog 内容记录,所以你可以在提交时包含相关变更文件,使你的 commit 更有价值。=> 00e4bd2

updateInternalDependencies(patch | minor | major)

内部依赖的更新策略。(由于依赖基本都是 workspace:* 或者 *,暂无需过多考虑)

ignore (array of packages)

ignore 用于忽略某些包的变更,如 @tardis-ksh/docs@test/** 包的变更不会被记录。格式 micromatch

{
  "ignore": ["@tardis-ksh/docs", "@test/**"]
}

* changelog

用于生成 changelog 的风格,或者自己定义

@changesets/changelog-github
{
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "tardis-ksh/demo-turborepo" }
  ]
}
@changesets/changelog-git

在本地使使用该 log 工具时需要在环境变量配置 GITHUB_TOKEN

{
  "changelog": "@changesets/changelog-git"
}
custom

getChangelogEntry.js

{
  "changelog": "./getChangelogEntry",
}

命令

changesets 命令行中,通过 space 空格选择,enter 回车确认或跳过。

changeset add 添加变更

pnpm changeset add
# or
pnpm changeset

应该在什么时候添加变更?和你 commit 节奏保持一致,如完成一个 feature 或修复一个 bug 时。建议在 commit 变更文件前,先执行 changeset add,这样 commit 时会包含变更文件。

简单了解 major、minor、patch,在版本中:major.minor.patchmajor 为主版本号, 可标记为不兼容的更改,minor 为次版本号,可标记为向后兼容的新功能,patch 为补丁版本号,可标记为向后兼容的问题修复。
通过 space 来选择变更的类型,enter 确认,或 enter 挑至下个选择

变更文件

在执行 add 后,会在 .changeset 目录生成变更文件,你可在这里继续修改变更 log,这将是此次变更的 changelog

changeset version 生成 版本变更 和 changelog

pnpm changeset version

problem with @changesets/changelog-github

add `GITHUB_TOKEN

如果你使用了该工具,你需要在本地环境变量中配置 GITHUB_TOKENcreate your token here,目前只需要 repo:statusread:user

for mac: ~/.zshrc~/.bashrc

$ vim ~/.zshrc
# add in your file
# export GITHUB_TOKEN = "some string"
$ source ~/.zshrc
$ echo $GITHUB_TOKEN

或者临时使用

GITHUB_TOKEN=your_token pnpm changeset version

Cannot read properties of null (reading ‘author’)

你需要将你本地的 commit 提交,否则 changelog 会报错,因为 changelog 会读取 commit 信息。

publish

pnpm changeset publish

在 CI(actions)中使用

name: Release

on:
  push:
    branches:
      - "main"

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4
        with:
          # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
          fetch-depth: 0

      - name: Setup pnpm
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20.x

      - name: Install Dependencies
        run: pnpm i

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          # This expects you to have a script called release which does a build for your packages and calls changeset publish
          publish: pnpm release
          commit: 'chore: release packages'
          title: 'chore: release packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: if a publish happens
        if: steps.changesets.outputs.published == 'true'
        # You can do something when a publish happens.
        run: |
          echo "down"

actions 中使用 changesets/action@v1。在本地添加完变更后,不需要再主动执行 changeset version 了,push 至分支,将由 changesets/action 自动执行。
changesets/action 执行流程:发现变更,创建新分支生成版号和 changelog,创建 PR,手动同意后将再次触发该 actions,此时 changesets/action 会执行 publish 命令(由你定义),发布至 npm

处理 Pull requests - changeset-bot

安装 changeset-bot 后,在后续的 PR 中(除 changeset 自己的 PRBot 将会在 PR 中留下评论,以及是否需要创建变更。

结尾

完结 撒花~~~

Turborepo
changesets
changesets/action
https://juejin.cn/post/7129267782515949575