首页
/ Motrix TypeScript 代码规范实践:kebab-case 文件命名、按构建目标划分的别名体系与 Zod 共享 Schema

Motrix TypeScript 代码规范实践:kebab-case 文件命名、按构建目标划分的别名体系与 Zod 共享 Schema

2026-09-06 10:11:25作者:翟江哲Frasier

本文基于 Motrix 仓库中的 代码风格规则 展开,系统讲解该下载管理器的文件命名约定、TypeScript 导入与路径别名规则,以及跨层数据的 Zod 共享 Schema 契约。读完之后,你可以在 Motrix 代码库中正确创建/重命名文件、安全使用各构建目标(Electron 主进程、渲染进程、服务器、QuickJS 插件 Worker、Vitest 测试)暴露的路径别名,并知道哪些运行时契约必须收敛为单一 Zod schema,从而避免常见的跨层类型漂移与别名跨目标误用。

Biome 是格式与通用 Lint 的唯一归属

规则文档开篇就明确了一条分工原则:Biome 负责格式化和通用 lint 规则,仓库级规则文档不重复这些设置,只补充 Biome 覆盖不到的仓库特定边界。这条分工在仓库根目录的 biome.json 中可以得到完整印证,其中与代码风格直接相关的配置包括:

  • 文件命名强制 kebab-caselinter.rules.style.useFilenamingConvention 被设为 error 级别,filenameCases 仅允许 kebab-casebiome.json)。也就是说,命名错误不只是"风格问题",而是会让 pnpm run lint(即 biome check .)直接失败的硬错误。
  • 格式化基线:2 空格缩进、80 字符行宽、LF 换行(formatter 段,biome.json)。
  • 引号与分号:单引号、asNeeded 分号、JSX 双引号、ES5 尾逗号(javascript.formatter 段,biome.json)。
  • 测试文件宽松区**/*.test.{ts,tsx}**/*.spec.{ts,tsx}**/test/****/__tests__/** 等路径下的代码关闭了 noNonNullAssertionnoExplicitAnyoverrides 段,biome.json),与后文文件命名中 .test.spec 等约定后缀形成配套。
  • 导入自动整理assist.actions.source.organizeImports 开启,Biome 会按源码顺序自动组织 import 语句。

理解这一分工的实际意义在于:当你觉得"引号风格""分号""import 排序"这类问题时,答案永远在 biome.json 里,跑 pnpm run lint:fix 即可;而 .claude/rules/code-style.md 承载的是 Biome 管不了的仓库边界——命名后缀语义、别名可用性和 Schema 归属。

文件命名规范

命名规则总览

规则文档(.claude/rules/code-style.md)对命名给出的约定可归纳为四条:

对象 约定 说明
JavaScript、TypeScript、TSX 与样式文件 kebab-case task-manager.tsuse-add-task-form.ts
约定性限定后缀 保持小写 .test.spec.e2e.integration.darwin.win32
导出的类与 React 组件 PascalCase 文件名仍是 kebab-case,仅导出符号用 PascalCase
Rust 与 Python snake_case Cargo 二进制入口可用 kebab-case 以匹配可执行文件名

这里有一个容易混淆的点:文件命名与符号命名是两套体系。仓库中大量文件形如 src/core/task/task-manager.tssrc/core/engine/engine-supervisor.ts,文件名全部是 kebab-case,但文件内部导出的类、组件保持 PascalCase——规则文档专门用一条约定锁死了这一点,避免"文件名是连字符、导出名也跟着变成 kebab"的漂移。

后缀语义也有明确划分:.test / .spec 对应单元测试(Vitest 的 include 模式为 src/**/*.test.{ts,tsx},见 vitest.config.ts),.e2e / .integration 对应端到端与集成测试(如 tests/e2e/nat-main.e2e.test.ts),.darwin / .win32 对应平台专属实现(如 src/core/probe/disk-probe-darwin.tssrc/core/probe/disk-probe-win32.ts),全部保持小写以与主文件干并列识别。

pnpm run check:file-names 的实际校验逻辑

规则要求"新增或重命名文件后运行 pnpm run check:file-names"。该脚本在 package.json 中注册为 node scripts/check-file-names.mjs,其实现(scripts/check-file-names.mjs)值得细看,它把规则文档中的每一条命名约定翻译成了可执行的检查:

  • kebab-case 扩展名集合scripts/check-file-names.mjs):.cjs.css.cts.js.jsx.mjs.mts.scss.ts.tsx 共 10 种,均要求文件干匹配 ^[a-z0-9]+(?:-[a-z0-9]+)*$
  • snake_case 扩展名集合scripts/check-file-names.mjs):.py.rs,匹配 ^[a-z0-9]+(?:_[a-z0-9]+)*$
  • Cargo 二进制例外:位于 src/bin/ 下的 .rs 文件放宽为"snake_case 或 kebab-case"(正则 ^[a-z0-9]+(?:[-_][a-z0-9]+)*$scripts/check-file-names.mjs)。这正是规则文档中"Cargo binary entrypoints may use kebab-case to match the executable name"一条的执行依据——例如 packages/native-host 这类 Rust 子包的 main.rssrc/bin/ 下的入口文件名可以带连字符以便产物可执行名可读;
  • 排除前缀docs/graphify-out/ 不参与检查(scripts/check-file-names.mjs);
  • 扫描范围:通过 git ls-files --cached --others --exclude-standard 枚举所有 Git 可见文件(含未跟踪的新文件),意味着新建文件也会被扫到,而不是只对已提交代码生效;
  • 失败行为:任何违例都会逐条打印 Invalid code file names 并令进程以非零码退出,因此可以安全接入 CI 或 pre-commit 流程。

换言之,规则文档中的命名约定在仓库里有三重执行保障:Biome 的 useFilenamingConvention(编辑器与 lint 阶段)、check:file-names 脚本(全仓库文件干级别,含 Biome 不覆盖的 Rust/Python 与 Cargo 例外)、以及约定性后缀与 Biome overrides 的测试目录匹配(*.test.* / *.spec.* 的 lint 宽松规则依赖这些后缀)。

导入与路径别名

import typenode: 前缀

两条导入层面的硬性约定:

  1. 类型专用导入必须使用 import type。这与 tsconfig.json 中开启的 verbatimModuleSyntax: true 是配套的——该编译选项要求模块语句"所见即所得",不写 type 修饰的类型导入会在构建时保留下来,导致运行时因目标模块没有该值导出而失败。
  2. Node 内建模块必须使用 node: 前缀,如 import path from 'node:path'。仓库源码与脚本中普遍如此(例如 scripts/check-file-names.mjs 开头的 node:child_processnode:path)。

别名优先,但相对导入有豁免

规则约定:优先使用已配置的别名而非深层相对导入(例如 ../../shared/... 这类跨越目录层的引用),但同目录(sibling)或上一级(parent)的本地导入可以保持相对路径。这是一个务实的取舍:别名解决"跨层/跨目录"的可读性与稳定性,而相邻文件之间 ./xxx 既短又清晰,强制走别名反而降低信息量。

别名可用性按构建目标划分

这是规则文档中最容易踩坑的部分。Motrix 是 Electron + 服务器 + 插件 Worker 的多目标工程,tsconfig.json 声明了全部五个别名,但每个构建目标只暴露其中一个子集,这一点在各 Vite 配置的 resolve.alias 中得到逐字印证:

目标 配置文件 实际暴露的别名
Electron 主进程 vite.main.config.ts @shared@core@main
渲染进程 vite.renderer.config.ts @shared@renderer
preload 桥 vite.preload.config.ts @shared
服务器(无 Electron 依赖) vite.server.config.ts @shared@core@server
QuickJS 插件 Worker vite.worker.config.ts @shared@core
Vitest 单元测试 vitest.config.ts @shared@core@renderer@server@test-utils故意没有 @main

tsconfig.json 中的 paths 则同时声明了 @shared/*@core/*@renderer/*@main/*@test-utils/* 五个别名,服务于编辑器智能提示与类型检查,并不代表运行时处处可用。

规则文档特别点出两个典型陷阱,均可从配置中直接验证:

  • Vitest 故意不暴露 @mainvitest.config.ts 的别名表确实只有 @shared@core@renderer@server@test-utils 五项。这意味着测试代码若引用 @main/...,类型检查会通过但测试运行时会解析失败——因为 src/main 深度依赖 Electron API,本就不该被测试直接导入。
  • QuickJS Worker 只暴露 @shared@corevite.worker.config.ts 印证了这一点。插件宿主(入口 src/core/plugin/host/quick-js-worker.ts,见 vite.worker.config.ts)运行在受限的 QuickJS 沙箱环境中,只能引用跨层共享代码与核心逻辑,@main@renderer@server 均不可用。

"使用别名前先查目标配置"这条建议的必要性,还可以从 scripts/check-boundaries.mjs 的分层边界规则中读出:它强制 src/core 不得导入 electronfastifysrc/shared 不得使用任何 node: API 与 Node 全局对象、src/renderer 不得导入 core/main 层、src/server 不得导入 electron@mainscripts/check-boundaries.mjs)。别名按目标裁剪,本质上就是这些分层依赖规则在模块解析层面的投影——@shared 之所以在所有目标都可用,正是因为它被设计成唯一不触碰 Node/Electron 特定 API 的层。

共享运行时契约:Zod 是唯一事实源

规则文档最后一节规定:对不受信任的外部数据与跨层载荷,一律使用 zod 的 Zod schema。具体约束有三条:

  1. 共享 schema 是其运行时约束、推断类型与默认值的唯一事实源——不允许为同一契约再创建平行接口(parallel interface)或手写校验器;
  2. 纯跨层 schema 放在 src/shared/schemas/,仓库中该目录已积累约 35 个文件(如 registryconformance 相关契约,biome.json 的 overrides 里专门提到了 registry.fixture.jsonregistry.conformance.json 两个 schema 数据文件);
  3. 宿主特定的校验留在其所属层内——例如仅 Electron 端关心的窗口/平台字段校验不应下沉到 src/shared/schemas/,仅服务器端关心的配置校验也不应上浮。

这一约定与仓库的边界检查是互相咬合的:scripts/check-boundaries.mjs 禁止 src/shared 引入任何 Node 特性,因此 shared 层的 schema 必须保持运行时无关;而类型契约收敛到 Zod 之后,check:schema-paritypackage.json 中的 node scripts/check-schema-parity.mjs 脚本)等校验才有明确的比对对象——推断类型与运行时约束出自同一个 schema,就不会出现"类型上说有、运行时校验不拦"的错位。

落地自查清单

在 Motrix 中提交代码前,可以按以下顺序自检:

  1. 新增/重命名文件后运行 pnpm run check:file-names,确认文件干符合 kebab-case(Rust/Python 为 snake_case,Cargo 二进制入口除外);
  2. 确认类型导入带 type 修饰、Node 内建模块带 node: 前缀,然后运行 pnpm run lint 交由 Biome 统一格式与 import 排序;
  3. 使用任何 @shared / @core / @main / @renderer / @server / @test-utils 别名前,先确认当前所在文件所属构建目标的 Vite/Vitest 配置确实暴露了该别名;
  4. 新增跨层数据结构时,直接在 src/shared/schemas/ 建立 Zod schema,禁止手写平行校验逻辑。

以上四条分别对应 scripts/check-file-names.mjsbiome.json、各 vite.*.config.tstsconfig.json 中可验证的实现,构成 Motrix 代码风格规则从"文档约定"到"工具执行"的完整闭环。

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