Chapter 09

Monorepo 与共享配置

一个 biome.json 管所有包,还是每包一份?多团队共存?Turborepo 缓存如何与 biome 配合?这一章给出一套实用方案。

9.1 Monorepo 里的三种布局

A. 根只放一份 biome.json
最简单——在仓库根跑 biome ci . 管全部。适合包之间风格一致、且包数量不多(< 20)。
B. 根一份基线 + 各包按需 extends
packages/config-biome 导出基线;每个需要定制的包自己 biome.json 里 extends 它。风格一致性 + 细粒度调整兼得,是 2026 年主流做法。
C. 完全独立
每包各自 biome.json,互不 extends。只在特殊情况(不同语言、遗留系统共存)采用。

9.2 推荐布局(方案 B)

my-monorepo/
├─ biome.json                   # 根:组织全局、一键全仓 lint
├─ packages/
│  ├─ config-biome/             # 共享基线包
│  │  ├─ package.json
│  │  └─ base.json              # ← 真正的基线
│  ├─ ui/
│  │  ├─ biome.json             # extends 基线 + React 规则
│  │  └─ src/...
│  └─ api/
│     ├─ biome.json             # extends 基线 + Node 规则
│     └─ src/...
└─ apps/
   ├─ web/biome.json
   └─ docs/biome.json

packages/config-biome/base.json

{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": { "recommended": true }
  },
  "organizeImports": { "enabled": true },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "semicolons": "always",
      "trailingCommas": "all"
    }
  }
}

packages/ui/biome.json

{
  "extends": ["@repo/config-biome/base.json"],
  "linter": {
    "rules": {
      "a11y": { "recommended": true },
      "style": { "useSelfClosingElements": "error" }
    }
  }
}

extends 的字符串可以是相对路径包名(走 Node 解析)——把 base.json 作为 package.json 的 exports 暴露即可。

packages/config-biome/package.json

{
  "name": "@repo/config-biome",
  "version": "0.0.0",
  "private": true,
  "exports": {
    "./base.json": "./base.json",
    "./react.json": "./react.json",
    "./node.json": "./node.json"
  }
}

9.3 根 biome.json 的作用

虽然每包有自己的 biome.json,仍建议留一份根配置,写 files.ignore 屏蔽 dist/build 产物,并允许跑 biome check . 做全仓扫描。

{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "extends": ["./packages/config-biome/base.json"],
  "files": {
    "ignore": [
      "**/dist/**",
      "**/build/**",
      "**/.next/**",
      "**/.turbo/**",
      "**/coverage/**"
    ]
  }
}
Biome 自动寻找最近 biome.json

packages/ui/src/foo.ts 上执行的格式化会读 packages/ui/biome.json;未命中则向上找,最终落到根。这保证 IDE 打开任何文件都能找到正确配置。

9.4 Turborepo 下的任务定义

// turbo.json
{
  "tasks": {
    "lint": {
      "inputs": ["src/**", "biome.json", "../../biome.json"],
      "outputs": []
    }
  }
}

要点:inputs 里把 biome.json 都列上——任一改动才会使缓存失效。

每包 package.json

{
  "scripts": {
    "lint": "biome ci ."
  }
}

根 package.json

{
  "scripts": {
    "lint": "turbo run lint",
    "lint:root": "biome ci .",
    "lint:affected": "turbo run lint --filter=[origin/main]"
  }
}

9.5 pnpm 工作区

无 Turborepo 时用 pnpm 的 --filter + 并发:

pnpm -r --parallel exec biome ci .

或直接在根跑一次全仓 ci——对 Biome 性能友好(单进程 AST 缓存优于多进程并发):

biome ci .

9.6 包内 override 的常见场景

// packages/api/biome.json
{
  "extends": ["@repo/config-biome/base.json"],
  "overrides": [
    {
      "include": ["src/generated/**"],
      "linter": { "enabled": false },
      "formatter": { "enabled": false }
    },
    {
      "include": ["src/scripts/**"],
      "linter": {
        "rules": { "suspicious": { "noConsoleLog": "off" } }
      }
    }
  ]
}

9.7 渐进推广:大仓库分阶段启用

老 monorepo 引入 Biome 常会遇到几千个历史 warning。分阶段:

  1. 第 1 阶段:只启用 formatter 全仓;lint 每包 enabled=false
  2. 第 2 阶段:每周选一个 package 开启 recommended lint、修完错误
  3. 第 3 阶段:开根配置 recommended,遗留特例写进 overrides
  4. 第 4 阶段:删除 ESLint,关闭旧规则残留

9.8 多团队协作:谁能改 base?

小结

Monorepo 里 Biome 的范式:一个 base 包 + 每包 extends + 少量 overrides。Turborepo 缓存要把 biome.json 列入 inputs。推广期可先 format、再 lint 分阶段开。下一章进入进阶主题——Biome v2 的 domain 规则组与 GritQL。