代码规范与 Biome 工具链
Mizuki 使用 Biome(@biomejs/biome v2.x)作为统一的代码格式化与 Lint 工具链,通过根目录的 biome.json 集中定义格式化风格、Lint 规则与作用域,并通过 package.json 中的 format / lint 脚本驱动日常开发流程。
Purpose and Scope
本页面覆盖 Mizuki 仓库中与"代码规范"相关的完整机制:
biome.json的全部配置块(files/formatter/assist/linter/javascript/overrides)及其设计意图;package.json中与 Biome 相关的脚本(format、lint)以及包管理器约束(only-allow pnpm);- Biome 作用域内/外的目录划分(哪些目录被排除、哪些框架文件被豁免部分规则);
- 添加 / 修改 / 检查代码时的实际控制流与常见故障模式。
以下内容有意留给兄弟页面,本页不展开:
- 内容仓库同步与构建流水线(
sync-content/prebuild/build等脚本逻辑)——见构建与部署相关页面; - TypeScript 编译器层面的类型检查(
type-check/astro check)——Biome 不做类型系统分析,类型约束由tsc与astro check承担; - 测试体系(
tests/*.test.mjs)。
Overview
Biome 是一个用 Rust 实现的"一体化"前端工具链,单进程同时提供 Formatter、Linter 与 Assist(代码辅助动作)。Mizuki 选择 Biome 而非 Prettier + ESLint 组合,从工程角度带来三个直接收益:
- 单一配置源:格式化与 Lint 规则都收敛在根目录
biome.json,不存在 Prettier/ESLint 规则打架的问题; - 单一依赖:仅引入
@biomejs/biome一个 devDependency(^2.5.5),安装体积与冷启动成本远低于"格式化器 + Linter + 插件集"的组合; - 统一命令:
biome format --write与biome check --write两条命令覆盖"格式化"与"检查并自动修复"两类日常操作。
Mizuki 是一个 Astro + Svelte 的站点工程,源码中混杂了 .ts / .mjs / .astro / .svelte 等多种文件形态。因此配置里专门用 files.includes 的排除模式把非代码资产(CSS、构建产物、内容目录)隔离在 Biome 作用域之外,并用 overrides 对组件类文件(.svelte / .astro / .vue)定向关闭一组会产生误报的规则——这是理解本项目代码规范时最关键的两个设计点。
仓库同时通过 "preinstall": "npx only-allow pnpm" 与 "packageManager": "pnpm@11.5.3" 强制统一包管理器,避免不同开发者用 npm/yarn 安装产生不一致的 lockfile,间接保证了 Biome 版本在团队内的一致性。
Architecture
架构要点解读:
- 入口极简:开发者只需要记两条命令——
pnpm format(只格式化)与pnpm lint(biome check --write,会同时跑 Lint + 自动安全修复 + 格式化)。biome check是 Biome 的聚合命令,等价于"Lint + Format + Assist"一次跑完。 - 配置即边界:
biome.json的files.includes用否定模式(!前缀)声明排除项,Biome 只处理未被排除的文件(主要是./src,因为脚本目标就是./src)。 - overrides 是组件文件的逃生舱:Astro/Svelte/Vue 这类"模板 + 脚本"混合文件在静态分析下很容易出现"编译期才可见"的变量使用,Mizuki 在
overrides中定向关闭 4 条规则来消除误报。 - 包管理器锁死:
preinstall钩子npx only-allow pnpm在 install 之前校验当前使用的包管理器,配合packageManager字段(pnpm@11.5.3,Corepack 可识别)保证所有贡献者拿到同一份依赖树。
核心配置详解:biome.json
整份配置基于 Biome 2.x schema($schema: https://biomejs.dev/schemas/2.0.0/schema.json),共 6 个顶层块。下面按数据流顺序逐一拆解。
文件作用域:files.includes
1"files": {
2 "includes": [
3 "**",
4 "!**/src/**/*.css",
5 "!**/dist/**/*",
6 "!**/node_modules/**/*",
7 "!**/.astro/**/*",
8 "!**/public/**/*",
9 "!**/demo/**/*",
10 "!**/scripts/**/*"
11 ]
12}Source: biome.json
写法是"先全量(**),再逐一减去(! 前缀)"。被排除的每类目录都有明确的工程理由:
| 排除模式 | 理由 |
|---|---|
!**/src/**/*.css | 样式由书写者手工排版,且 Biome 对 CSS 的格式化并非本项目诉求 |
!**/dist/**/* | astro build 产物,机器生成,格式化无意义且耗时 |
!**/node_modules/**/* | 第三方依赖,不属于本仓库规范管辖 |
!**/.astro/**/* | Astro 生成的缓存/类型目录 |
!**/public/**/* | 静态资源(图片、字体等二进制与非代码文件) |
!**/demo/**/* | 演示样例,独立于主站代码规范 |
!**/scripts/**/* | 构建辅助脚本目录,package.json 中的脚本(如 sync-content.js)都在此维护,被刻意排除在格式化与 Lint 之外 |
值得注意的是:vcs.enabled: false 且 useIgnoreFile: false,即 Biome 不会读取 .gitignore,排除逻辑完全自包含在 biome.json 内。这意味着即便某目录被 git 忽略规则覆盖,只要不在这里显式排除,Biome 仍会处理它(例如本地未提交的临时目录)。
格式化器:formatter
1"formatter": {
2 "enabled": true,
3 "indentStyle": "tab",
4 "lineWidth": 80
5}Source: biome.json
indentStyle: "tab":缩进使用制表符。这与biome.json自身文件也用 tab 缩进的事实相互印证,配置文件本身就是规范的一个样例。lineWidth: 80:80 列换行,Biome 的默认值;对内含大量 JSX/模板字符串的 Astro 组件保持了可读的横向长度。
Assist:自动整理 import
"assist": { "actions": { "source": { "organizeImports": "on" } } }Source: biome.json
organizeImports: "on" 表示在 biome check 时对 import 语句排序/去重并自动应用(安全修复)。设计意图是把"import 顺序"这种无争议的机械操作从 code review 中彻底移除。注意该动作在 Biome 2.x 中属于 assist 而非 linter,因此只有在跑 biome check(即 pnpm lint)时才会触发,单独跑 pnpm format 不会整理 import。
Linter 规则集
1"linter": {
2 "enabled": true,
3 "rules": {
4 "recommended": true,
5 "style": {
6 "noParameterAssign": "error",
7 "useAsConstAssertion": "error",
8 "useDefaultParameterLast": "error",
9 "useEnumInitializers": "error",
10 "useSelfClosingElements": "error",
11 "useSingleVarDeclarator": "error",
12 "noUnusedTemplateLiteral": "error",
13 "useNumberNamespace": "error",
14 "noInferrableTypes": "error",
15 "noUselessElse": "error"
16 }
17 }
18}Source: biome.json
以 recommended: true 为基线,再在 style 分组上叠加 10 条 error 级强化规则。逐条的设计意图:
| 规则 | 禁止 / 强制的写法 | 为什么 |
|---|---|---|
noParameterAssign | 不允许给函数参数重新赋值 | 避免参数语义在函数体内被偷换,便于阅读时建立稳定心智模型 |
useAsConstAssertion | x as const 优于 x as "literal" | 让字面量推导交给编译器,减少重复书写具体字面量 |
useDefaultParameterLast | 默认参数必须放在参数列表末尾 | 保证位置实参的绑定顺序符合直觉 |
useEnumInitializers | enum 成员必须显式给初值 | 防止隐式自增初值在成员被插入/重排时悄悄改变取值 |
useSelfClosingElements | 无子元素的标签必须自闭合(<br />) | 统一模板书写风格,Astro/Svelte 模板同样受约束 |
useSingleVarDeclarator | 一条声明只声明一个变量 | 拆分多变量声明,降低 diff 噪音 |
noUnusedTemplateLiteral | 非必要的模板字符串要写成普通字符串 | `hello` → "hello",去掉无意义的插值外壳 |
useNumberNamespace | 使用 Number.NaN / Number.parseInt 而非全局 NaN / parseInt | 全局数值 API 与 Number 命名空间统一 |
noInferrableTypes | 禁止写可推断的冗余类型标注 | let n: number = 1 → let n = 1,类型交给 TS 推断 |
noUselessElse | if 分支必然 return 时去掉 else | 消除缩进层级与无效分支 |
JS/TS 细节:javascript.formatter
1"javascript": {
2 "formatter": {
3 "quoteStyle": "double",
4 "semicolons": "always",
5 "trailingCommas": "all",
6 "arrowParentheses": "always"
7 }
8}Source: biome.json
四项都是强约束、零歧义的机械规则:双引号字符串、必写分号、所有位置(含末参数)尾随逗号、单参数箭头函数也带括号((x) => x)。尾随逗号能减少多行参数追加时的 diff 行数;箭头函数强制括号则让后续从单参数扩展到多参数时不改动首参位置。
组件文件豁免:overrides
1"overrides": [
2 {
3 "includes": ["**/*.svelte", "**/*.astro", "**/*.vue"],
4 "linter": {
5 "rules": {
6 "style": {
7 "useConst": "off",
8 "useImportType": "off"
9 },
10 "correctness": {
11 "noUnusedVariables": "off",
12 "noUnusedImports": "off"
13 }
14 }
15 }
16 }
17]Source: biome.json
对 .svelte / .astro / .vue 三类组件文件定向关闭 4 条规则:
useConst: off与noUnusedVariables: off:组件模板中声明的变量常被模板侧消费,静态分析看不到模板引用关系,强制会大量误报;useImportType: off与noUnusedImports: off:组件文件的 import 常常只用于类型上下文或被编译器处理(如 Svelte 5 的 runes、Astro 的 frontmatter 类型),保留为普通 import 更稳妥。
这是"规则基线 + 逃生舱"的典型分层:基线保证普通 TS/JS 文件的严格性,overrides 精准放宽到框架特性导致的误报场景,而不是整体降级推荐规则。
命令行工作流与核心流程
Mizuki 的 Biome 相关 npm scripts 定义在 package.json:
"format": "biome format --write ./src",
"lint": "biome check --write ./src",
"preinstall": "npx only-allow pnpm"Source: package.json
pnpm format→biome format --write ./src:只做格式化并写回文件。作用域被命令行参数进一步收窄到./src(与biome.json的files.includes取交集)。pnpm lint→biome check --write ./src:biome check是聚合命令,一次执行 Linter(含 recommended + style 强化规则)+ Formatter + Assist(organizeImports),--write只应用"安全修复",无法自动修复的问题仍会以诊断形式输出并让命令以非零码退出。preinstall:在任何依赖安装动作之前由 npm/pnpm 生命周期触发npx only-allow pnpm,若当前不是 pnpm 则直接失败,配合"packageManager": "pnpm@11.5.3"(见 package.json)把依赖树与 Biome 二进制版本锁死在团队一致状态。
一次典型的"提交前检查"时序如下:
流程要点:
- 配置读取发生在扫描之前,因此
overrides的.svelte/.astro/.vue匹配在诊断阶段就已生效,而不是事后过滤; --write只回写"安全"修复;对于style分组中标记为error且无自动修复动作的规则,需要开发者手动改写;- 由于
files.includes排除了scripts/,./src之外的scripts/*.mjs即使被命令行包含也不会受biome.json当前排除表影响——但本仓库两条脚本的目标都锁定在./src,因此scripts/实际上完全不被 Biome 触碰(这也解释了为何build脚本中调用的node scripts/*.mjs不会因格式化改动而变化)。
Usage Examples
场景一:日常开发循环
1# 安装(会先触发 preinstall 校验,仅允许 pnpm)
2pnpm install
3
4# 只格式化 src
5pnpm format
6
7# Lint + 自动安全修复 + 整理 import(推荐提交前执行)
8pnpm lintSource: package.json
场景二:为组件文件豁免误报(配置改法)
当 .astro / .svelte 组件因为模板引用触发误报时,应调整 overrides 而不是全局降级规则。现有配置已示范了最小豁免集:
1{
2 "includes": ["**/*.svelte", "**/*.astro", "**/*.vue"],
3 "linter": {
4 "rules": {
5 "style": {
6 "useConst": "off",
7 "useImportType": "off"
8 },
9 "correctness": {
10 "noUnusedVariables": "off",
11 "noUnusedImports": "off"
12 }
13 }
14 }
15}Source: biome.json
场景三:把新目录纳入规范
若未来引入新的源码目录(例如 lib/),有两种做法:
- 修改
package.json的脚本目标:biome check --write ./src ./lib; - 或从
biome.json的files.includes中移除对应!排除项(若该目录此前被排除)。
二者是"命令行作用域"与"配置作用域"的正交关系:最终生效范围是两者交集。
Configuration Options
biome.json 完整配置项一览(以仓库实际值为准):
| 配置项 | 类型 | 仓库取值 | 说明 |
|---|---|---|---|
$schema | string | https://biomejs.dev/schemas/2.0.0/schema.json | Biome 2.0 schema,供编辑器校验 |
vcs.enabled | boolean | false | 不集成 VCS(不读 git 状态) |
vcs.clientKind | string | "git" | 客户端类型(当前未启用) |
vcs.useIgnoreFile | boolean | false | 不读取 .gitignore,排除逻辑自包含于 files.includes |
files.includes | string[] | ["**", "!**/src/**/*.css", "!**/dist/**/*", "!**/node_modules/**/*", "!**/.astro/**/*", "!**/public/**/*", "!**/demo/**/*", "!**/scripts/**/*"] | 作用域 = 全量减去排除项 |
formatter.enabled | boolean | true | 启用格式化器 |
formatter.indentStyle | enum | "tab" | 缩进用制表符 |
formatter.lineWidth | number | 80 | 80 列换行 |
assist.actions.source.organizeImports | enum | "on" | biome check 时自动整理 import |
linter.enabled | boolean | true | 启用 Linter |
linter.rules.recommended | boolean | true | 推荐规则全开作为基线 |
linter.rules.style.* | enum | 10 条规则全部 "error" | 见上文"Linter 规则集"表格 |
javascript.formatter.quoteStyle | enum | "double" | 字符串用双引号 |
javascript.formatter.semicolons | enum | "always" | 必写分号 |
javascript.formatter.trailingCommas | enum | "all" | 所有位置尾随逗号 |
javascript.formatter.arrowParentheses | enum | "always" | 单参箭头函数也带括号 |
overrides[0].includes | string[] | ["**/*.svelte", "**/*.astro", "**/*.vue"] | 组件文件匹配 |
overrides[0].linter.rules | object | 4 条规则 "off" | useConst / useImportType / noUnusedVariables / noUnusedImports |
相关 npm scripts:
| 脚本 | 命令 | 作用 |
|---|---|---|
format | biome format --write ./src | 仅格式化并写回 |
lint | biome check --write ./src | Lint + 格式化 + assist,应用安全修复 |
preinstall | npx only-allow pnpm | 强制使用 pnpm |
type-check | tsc --noEmit | 类型检查(与 Biome 职责分离) |
check | astro check | Astro 项目的类型/诊断检查(与 Biome 职责分离) |
Source: package.json
API Reference
Biome 在本仓库以 CLI 形式消费,不暴露编程 API。实际使用的两条命令:
biome format --write <path>
- 参数:
--write将格式化结果直接写回磁盘(缺省只做 dry-run 输出 diff);<path>在本仓库固定为./src。 - 行为:按
formatter.*与javascript.formatter.*的取值重排空白、引号、分号、尾随逗号;不执行任何 Lint 规则、不整理 import。 - 返回:被修改文件数;存在无法格式化的文件时报错并以非零码退出。
biome check --write <path>
- 参数:
--write只应用安全修复(含部分 lint 修复、格式化、organizeImports);<path>固定为./src。 - 行为:一次性执行 Linter(
recommended+style强化 +overrides豁免)、Formatter、Assist。 - 返回:诊断摘要;仍有未修复的
error级问题时以非零码退出(可用作 CI 门禁)。 - 注意:
useEnumInitializers、noParameterAssign等部分 style 规则没有自动修复动作,命中时必须人工改写代码。
Failure Modes, Edge Cases & Concurrency
作用域边界误判
scripts/与demo/不受规范约束:修改这些目录下的代码不会触发任何 Biome 诊断。若希望统一规范,需同时调整biome.json的排除表与 npm scripts 的目标路径。.gitignore不生效:vcs.useIgnoreFile: false意味着本地未提交、未被显式排除的文件仍会被处理。新增生成目录时必须同步更新files.includes。- CSS 被整体跳过:
!**/src/**/*.css只匹配src内的 CSS;若其他目录存在 CSS,理论上仍会被处理(但命令行目标./src使其实际不触发)。
组件文件的规则豁免是"开关"而非"降级"
overrides 中 "off" 是完全关闭。若某 .svelte 文件确实存在真实未使用变量,Biome 不会提示——这类问题需依赖 astro check / tsc 或人工 review 兜底(TypeScript 侧的 noUnusedLocals 由 tsconfig.json 控制,本页不展开)。
--write 的修复安全边界
biome check --write 仅应用安全修复。对于被标为 error 的 style 规则,若该规则无 fixer(如 useEnumInitializers 需要人来决定每个成员的取值),命令会保留诊断并返回非零退出码。把它当作 CI 门禁时要注意:门禁失败 ≠ 有 bug,多数情况只是风格未对齐,跑一次 pnpm lint 即可收敛大部分。
并发与幂等
- Biome 格式化是幂等的:重复执行
pnpm format第二次不会产生 diff(稳定输出是格式化器的基本契约)。 - 多进程并发对同一文件执行
--write理论上存在竞态,但本仓库的脚本都是全量单进程跑./src,不存在并行实例。
包管理器不一致
使用 npm/yarn 安装会在 preinstall 阶段被 only-allow pnpm 直接拒绝;绕过该钩子(如 --ignore-scripts)可能导致依赖树与 lockfile 漂移,进而使 Biome 版本不一致、格式化输出出现无意义 diff。
Performance / Operational Notes
- 零插件模型:Biome 内置解析器覆盖 JS/TS/JSON(及本仓库关心的
.astro/.svelte文本),无需像 ESLint 那样加载一堆插件与解析器配置,pnpm lint在中型项目上通常秒级完成。 - 排除产物目录的收益:
dist/.astro/node_modules/public均在扫描前就被过滤,避免了每次 check 遍历数万个构建产物文件。 - 与构建链的关系:
build脚本(astro build+ 若干node scripts/*.mjs后处理)不包含 Biome 步骤——格式与 Lint 属于开发期约束,不进入生产构建路径;predev/prebuild钩子只做内容同步(sync-content.js),同样不触发 Biome。这一点保证了 CI 构建时长不受格式化影响。 - 版本升级:Biome 主版本间(1.x → 2.x)配置 schema 与规则名有破坏性变化(本仓库
$schema指向2.0.0)。升级@biomejs/biome(当前^2.5.5)时建议先跑biome migrate再全量pnpm lint,并注意规则名变动。
Extension Points
- 新增规则:在
linter.rules对应分组(style/correctness/suspicious等)下追加规则名与级别即可,推荐保持"error"语义(能被 CI 拦截)。 - 新增豁免面:优先扩展
overrides数组,用includes匹配文件集合、只关闭确有误报的规则;避免直接改recommended基线。 - 扩展作用域:新源码目录要同时改 npm scripts 目标与
files.includes,二者交集才是最终处理范围。 - 目录级配置:Biome 2.x 支持嵌套
biome.json,可为特殊子目录(如未来独立的组件库)单独建配置,父配置的排除/覆盖逻辑会被继承并叠加。
Related Links
- Biome 官方文档:https://biomejs.dev/ (规则清单、CLI 用法、schema 说明)
- biome.json — 本仓库全部格式化与 Lint 配置的唯一来源
- package.json —
format/lint/preinstall脚本与packageManager字段 - tsconfig.json — 类型检查侧的约束(与 Biome 职责互补,见兄弟页面)
- 构建与内容同步脚本(
scripts/目录)— 被 Biome 排除,属构建流水线主题,见对应兄弟页面