首页
/ Plane Monorepo Lint 体系实战:OxLint 单一根配置、逐包警告预算与 Pre-commit 自动修复

Plane Monorepo Lint 体系实战:OxLint 单一根配置、逐包警告预算与 Pre-commit 自动修复

2026-09-06 18:34:56作者:柯茵沙

Plane 是一个采用 pnpm + Turbo 组织的 TypeScript 单仓库(monorepo),其代码风格与潜在缺陷的静态检查完全由 OxLint 承担。本篇基于仓库内的 docs/linting.md 展开,系统讲解 Plane 如何用一份根级 .oxlintrc.json 覆盖全部应用与包、如何通过 pnpm check:lint / pnpm fix:lint 与 Turbo 过滤器执行检查、如何用逐包 --max-warnings 预算逐步收敛存量告警,以及 Husky + lint-staged 如何在提交前自动格式化并修复。读完本文,你可以完整理解并复用这套"单一根配置 + 任务编排 + 警告预算 + 提交前门禁"的 Lint 工程化方案。

为什么选择 OxLint:单二进制、免构建的 Lint 管线

docs/linting.md 开篇即给出 Plane 选用 OxLint 的三条核心理由:

  1. Single Root Config(单一根配置):仓库根部只有一份 .oxlintrc.json,即可处理所有 packages 与 apps,无需在每个子包里重复维护 ESLint 配置;
  2. No Build Required(无需构建产物):OxLint 不依赖 TypeScript 的构建产物,Lint 与 build 相互独立——即使 dist/、类型产物尚未生成,检查也能照常运行;
  3. Plugin Coverage(插件覆盖):一次启用 reacttypescriptjsx-a11yimportpromiseunicornoxc 七个插件族,覆盖 React 组件规范、TS 类型安全写法、无障碍(a11y)、模块导入顺序、Promise 误用与代码风格等常见检查维度。

文档同时提到,OxLint 是一个 Rust 编写的单二进制工具,官方宣称速度可达 ESLint 的 50~100 倍,运行时零 Node.js 依赖。这两点对大型 monorepo 意义很大:检查耗时不再随 tsc 编译链路增长,且 CI 中不需要先装完整 Node 工具链就能跑 Lint。

这套选型在仓库中有明确的落点——根 package.jsondevDependencies 通过 pnpm catalog 统一声明了 oxlintoxfmtturbohuskylint-staged,而具体版本集中锁定在 pnpm-workspace.yamlcatalog 段中:oxlint1.51.0oxfmt0.35.0turbo2.9.18husky9.1.7lint-staged16.2.7。也就是说,全部工作区共享同一版本,避免各子包引入不同 OxLint 造成的规则行为漂移。

如何运行 Lint:根脚本、Turbo 过滤与逐包命令

根目录命令

按照 docs/linting.md,在仓库根目录执行:

# 检查 lint 错误
pnpm check:lint

# 自动修复 lint 错误
pnpm fix:lint

这两个脚本定义在根 package.json 中:

"check:lint": "turbo run check:lint",
"fix:lint": "turbo run fix:lint",
"check": "turbo run check",
"fix": "turbo run fix"

它们并不是直接调用 oxlint,而是交给 Turbo 做任务编排。turbo.json 中对应的任务定义为:

"check:lint": {
  "inputs": ["$TURBO_DEFAULT$", "!**/*.md"],
  "outputs": []
},
"fix:lint": {
  "inputs": ["$TURBO_DEFAULT$", "!**/*.md"],
  "outputs": []
}

有三个细节值得注意:

  • **/*.md 被显式排除在缓存输入之外——修改 Markdown 不会使 Lint 缓存失效;
  • outputs: [] 表明 Lint 不产生构建产物,Turbo 仅按输入文件哈希做增量缓存;
  • turbo.json.oxlintrc.json 列入 globalDependencies,意味着根配置文件一旦变化,所有包的 Lint 缓存会整体失效重跑——这正是"单一根配置"模式下防止配置改动被缓存掩盖的保险设计。

同时,check 任务聚合了三类静态检查,构成完整的质量门禁:

"check": {
  "dependsOn": ["check:format", "check:lint", "check:types"]
}

即格式检查(oxfmt --check)、Lint 检查(oxlint)与类型检查(tsc --noEmit)三者缺一不可。

对指定包运行

当只需要验证某一个包时,文档给出的方式是:

pnpm turbo run check:lint --filter=@plane/ui

这里的 @plane/ui 对应 packages/ui/package.json(该包 name 即为 @plane/ui)。同样可以用 --filter 精确到 web@plane/editor 等任意工作区成员。

每个子包内的真实 oxlint 命令与"警告预算"

文档只展示了入口命令,而各子包的 check:lint 脚本揭示了仓库的一个重要实践——逐包设置 --max-warnings 警告预算。各子包的实际脚本为(均直接调用 oxlint):

包(name) 脚本 警告预算
apps/web (web) oxlint --max-warnings=11957 . 11957
apps/admin (admin) oxlint --max-warnings=759 . 759
apps/space (space) oxlint --max-warnings=676 . 676
apps/live oxlint --max-warnings=119 . 119
packages/editor oxlint --max-warnings=416 . 416
packages/propel oxlint --max-warnings=3605 . 3605
packages/ui (@plane/ui) oxlint --max-warnings=66 . 66
packages/utils oxlint --max-warnings=38 . 38
packages/i18n oxlint --max-warnings=9 . 9
packages/services oxlint --max-warnings=6 . 6
packages/constants oxlint --max-warnings=2 . 2
packages/types oxlint --max-warnings=1 . 1
packages/shared-state oxlint --max-warnings=0 . 0
packages/logger oxlint --max-warnings=0 . 0

--max-warnings=N 的语义是:错误(error)仍然会导致检查失败,而警告(warning)的数量超过 N 时任务才会失败。这是一种典型的"存量收敛"策略——迁移到 OxLint 初期存量警告较多,先把预算锁死在现状值上,之后只减不增,最终把预算压到 0(如 packages/loggerpackages/shared-state 已经做到零容忍)。新增代码引入的新警告会立刻超出预算导致 CI 失败,从而保证代码库质量单调改善。

对应的 fix:lint 脚本则统一为 oxlint --fix .(如 apps/web/package.json),即对当前包目录做尽力自动修复。

根配置 .oxlintrc.json 逐项解析

docs/linting.md 称"一份根配置处理所有包和应用",下面结合 .oxlintrc.json 的实际内容逐项说明。

插件与环境

"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "jsx-a11y", "import", "promise", "unicorn", "oxc"],
"env": {
  "browser": true,
  "node": true,
  "es2024": true
}
  • plugins 与文档的"Plugin Coverage"一一对应;
  • env 同时声明 browsernode——因为仓库里既有面向浏览器的 React 应用(apps/webapps/spaceapps/admin),也有面向 Node 的服务端代码(apps/live 中的 Hocuspocus/Yjs 协作服务,以及 packages/decoratorspackages/logger 等);es2024 则允许使用最新 ES 语法内置对象而不被误报 no-undef

类别化规则等级

"categories": {
  "correctness": "warn",
  "suspicious": "warn",
  "perf": "warn"
}

文档中给出的类别表为:

类别 文档中的等级 作用
correctness error 会导致运行时错误的真实缺陷
suspicious warn 大概率是写错的代码模式
perf warn 性能反模式

需要注意:当前仓库的 .oxlintrc.json 中三个类别实际均设为 warn,而非文档表格中 correctnesserror。结合上文各包脚本中的 --max-warnings 预算即可理解这一取舍:统一按 warning 上报,再用逐包预算控制总量。若希望某条规则更严格,可在 rules 段单独覆盖。

框架级设置

"settings": {
  "react": { "version": "19.0" },
  "jsx-a11y": { "polymorphicPropName": "as" }
}
  • react.version: 19.0 告诉 React 插件按 React 19 语义检查(仓库 catalog 中 React 版本为 19.2.8,与之一致),从而不会误报已废弃的旧版约束;
  • jsx-a11y.polymorphicPropName: "as" 是 Plane 这类重度使用"多态组件"(通过 as 属性切换渲染标签)代码库的关键配置:声明了它之后,<Link as="button"> 这类写法在无障碍规则下的标签匹配判断才能正确工作。

忽略路径

"ignorePatterns": [
  ".cache/**",
  ".next/**",
  ".react-router/**",
  ".storybook/**",
  ".turbo/**",
  ".vite/**",
  "*.config.{js,mjs,cjs,ts}",
  "build/**",
  "coverage/**",
  "dist/**",
  "**/public/**",
  "storybook-static/**"
]

docs/linting.md 列出的忽略范围(node_modules/dist/build/.next/.turbo/*.config.{js,mjs,cjs,ts}、public 目录、coverage、storybook-static)一致并有所扩展。配置文件的忽略规则值得注意:所有 vite.config.tspostcss.config.js 等构建配置都不参与 Lint,避免工具生成的样板代码产生噪音。

规则覆盖(rules)

"rules": {
  "react/react-in-jsx-scope": "off",
  "react/prop-types": "off",
  "unicorn/filename-case": "off",
  "unicorn/no-null": "off",
  "unicorn/prevent-abbreviations": "off",
  "no-unused-vars": [
    "warn",
    {
      "argsIgnorePattern": "^_",
      "varsIgnorePattern": "^_",
      "caughtErrorsIgnorePattern": "^_",
      "destructuredArrayIgnorePattern": "^_",
      "ignoreRestSiblings": true
    }
  ]
}

对照文档的"Additional rule overrides",可以逐条确认其动机:

  • react/prop-types: off——文档说明"TypeScript 已经负责 props 校验",在 TS 项目中保留 prop-types 只会产生噪音;同理 react/react-in-jsx-scope 关闭是因为新版 JSX Transform 不再要求 import React
  • no-unused-vars 保留为 warn,但通过 argsIgnorePattern / varsIgnorePattern^_ 前缀模式,允许用下划线前缀显式标记"有意未使用"的参数、变量与捕获的异常;ignoreRestSiblings: true 则放行 const { a, ...rest } = obj 中解构透传的场景——这在 React props 转发中极为常见;
  • 三条 unicorn 规则(filename-caseno-nullprevent-abbreviations)关闭,对应文档所说的"Several noisy unicorn rules disabled":文件名大小写约定、禁止 null(JS/TS 项目中常需显式空值)与命名缩写限制(如禁止 objinfo 这类缩写)在大型存量代码库中会产生海量误报级别的告警,逐条关闭是务实选择。

覆盖范围:哪些代码被 Lint,哪些不会

文档明确 Lint 配置应用于以下全部 TypeScript / JavaScript 文件:

  • 应用层:apps/webapps/adminapps/spaceapps/live
  • 包层:packages/ 下的所有包(uipropeleditori18ntypesutilsservicesconstantsshared-statehooksloggerdecoratorscodemods 等)。

这与 pnpm-workspace.yaml 中工作区声明吻合:

packages:
  - apps/*
  - packages/*
  - "!apps/api"
  - "!apps/proxy"

apps/api(Django 后端)与 apps/proxy(Caddy 网关)不属于 pnpm 工作区,因此天然不在 JS/TS Lint 范围内。

抑制警告:eslint-disable 的向后兼容

docs/linting.md 专门强调了向后兼容:OxLint 支持 eslint-disable 注释,从 ESLint 迁移而来仓库中已有的内联抑制无需批量改写即可继续生效。文档给出的两种写法:

// 单行抑制
// eslint-disable-next-line no-unused-vars
const data = response;

// 块级抑制
/* eslint-disable no-unused-vars */
// ... code
/* eslint-enable no-unused-vars */

文档同时给出了使用约束:"Please use sparingly —— most warnings indicate real issues that should be fixed."(请克制使用,多数警告指向应当修复的真实问题)。结合上文 --max-warnings 预算机制可以理解:每一次注释抑制都在消耗该包的警告预算额度,滥用会让预算失真、削弱 CI 门禁的判别力。因此抑制注释应当只用于确认无误报的边界场景(如类型断言、跨模块契约代码),并尽量附注释说明原因。

Pre-commit 钩子:Husky + lint-staged + oxfmt + oxlint

docs/linting.md 指出提交时 Lint-staged 会经由 Husky 自动运行,做两件事:

  1. oxfmt 格式化本次暂存的文件;
  2. OxLint 自动修复可修复项,且带 --deny-warnings——即修复后若仍存在警告,提交直接失败。

仓库中的实际链路可以完整验证:

  • package.json"prepare": "husky" 脚本在 pnpm install 时激活钩子;
  • 钩子本体 .husky/pre-commit 只有一行:pnpm lint-staged
  • 真正执行规则由根 package.jsonlint-staged 配置定义:
"lint-staged": {
  "*.{js,jsx,ts,tsx,cjs,mjs,cts,mts,json,css,md}": [
    "pnpm exec oxfmt --no-error-on-unmatched-pattern"
  ],
  "*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
    "pnpm exec oxlint --fix --deny-warnings"
  ]
}

两条规则按文件类型分工:格式规则(含 JSON、CSS、Markdown)交给 oxfmt;JS/TS 代码则交给 oxlint --fix --deny-warnings 做"能修则修、修不干净则拦下"的门禁。这意味着一旦提交因 Lint 失败,开发者必须先修复警告再提交,存量警告不可能被悄悄带进主干。

配套的格式化配置 .oxfmtrc.json 同样位于根目录,被 turbo.json 列入 globalDependencies(与 .oxlintrc.json 同等待遇)。其关键项为:printWidth: 120tabWidth: 2trailingComma: es5,并对 cn/clsx/cva 三个函数内的 Tailwind 类名做排序(sortTailwindcss,样式表锚定在 packages/tailwind-config/index.css);另外 packages/codemods 下的文件单独覆写为 printWidth: 80

编辑器集成与完整检查流程

文档建议安装 VS Code 的 OxLint extension,以获得与 CI 规则完全一致的逐行错误/警告提示,避免"本地编辑器一片绿、CI 却挂掉"的偏差。

把上述机制串起来,Plane 的一次完整质量检查流程是:

  1. 本地逐次提交:Husky 触发 lint-staged,对暂存文件执行 oxfmt 格式化 + oxlint --fix --deny-warnings 修复门禁;
  2. 本地全量/按包检查pnpm check:lint(全仓)或 pnpm turbo run check:lint --filter=<pkg>(单包),由 Turbo 做增量缓存、由各包 --max-warnings 预算把关;
  3. CI 三合一门禁pnpm check 等价于依次跑 check:formatoxfmt --check)、check:lintcheck:typestsc --noEmit),覆盖格式、静态缺陷与类型三个维度。

参考文件

需要说明的适用前提:以上全部命令基于仓库根 package.json 声明的运行环境(Node >=22.22.0pnpm@11.3.0),各包警告预算(--max-warnings 数值)是当前仓库快照下的存量值,后续随着警告清零会持续下调。

登录后查看全文
热门项目推荐
相关项目推荐