编程 Biome 深度拆解:一个 Rust 工具链如何终结 JavaScript 十年之痛——从 Rome 遗产到 500 条 Lint 规则的架构哲学

2026-08-03 04:12:16 +0800 CST views 8

Biome 深度拆解:一个 Rust 工具链如何终结 JavaScript 十年之痛——从 Rome 遗产到 500 条 Lint 规则的架构哲学

背景:JavaScript 工具链的十年之痛

如果你是一个前端开发者,你的项目里大概率同时装着 ESLint、Prettier、stylelint、import-sort、editorconfig-checker……甚至更多。每个工具有自己的配置文件、自己的插件生态、自己的运行时开销。一个中等规模的 React 项目,光工具链的 node_modules 就能占掉 200MB+,每次 eslint --fix 跑一遍要 30 秒,Prettier 再跑一遍又是 10 秒。

这不是个别现象——这是 JavaScript 生态的系统性问题。

2023 年 8 月,一个名为 Biome 的项目横空出世。它的前身是 Meta 孵化的 Rome Tools,由 Sebastian McKenzie(Babel 创始人)在 2020 年创建,目标是用 Rust 重写整个前端工具链。Rome Tools Inc. 在 2023 年关闭后,核心团队 fork 了 Rome,创建了 Biome——名字取自 "Bis"(拉丁语"二")+ "Rome",意为"第二个 Rome"。

三年后的 2026 年 6 月,Biome v2.5 发布,拥有 500 条 Lint 规则,内置格式化器、导入组织器、HTML 无障碍检查、跨语言 CSS 类检查,以及一个不需要 TypeScript 编译器的类型推断引擎。这不是一个"更好的 Prettier"——这是一个从底层重新设计的 JavaScript 工具链。

本文将从架构层面深度拆解 Biome 的每一个核心组件,带你看懂一个 Rust 项目如何用 CST(具体语法树)增量格式化跨文件分析插件系统终结 JavaScript 工具链的十年之痛。

第一层:CST——一切的基石

为什么不是 AST?

传统的 JavaScript 解析器(如 Babel、TypeScript Compiler)产出的是 AST(抽象语法树)。AST 只保留语法的语义结构,丢弃了格式信息——空格、缩进、注释位置、换行符全部消失。这意味着基于 AST 的格式化器必须从零重建输出,而基于 AST 的 Lint 规则无法感知格式上下文。

Biome(继承自 Rome)选择了完全不同的路径:CST(Concrete Syntax Tree,具体语法树)。CST 保留了源代码的所有信息,包括:

  • 每个 token 的精确位置(行、列、字节偏移)
  • 所有空白符和注释(作为 "trivia" 附着在节点上)
  • 完整的语法结构
// Biome 内部的 CST 节点定义(简化版)
pub struct JsVariableDeclarator {
    id: JsBindingIdentifier,
    initializer: Option<JsInitializerClause>,
    // trivia(空白和注释)附着在 token 上,不在节点里
}

pub struct JsCallExpression {
    callee: AnyJsExpression,
    type_args: Option<TsTypeArguments>,
    arguments: JsCallArguments,
}

Green Tree 与 Red Tree:Rust 的零拷贝魔法

Biome 的 CST 实现采用经典的 Green/Red Tree 模式(源自 IntelliJ IDEA 的 PSI):

Green Tree(不可变语法树)

  • 不可变、可共享、按需创建
  • 存储所有 token 和 trivia
  • 可以通过 rowan crate 进行高效的序列化和反序列化
  • 一旦创建就不会改变

Red Tree(可变语法树)

  • 基于 Green Tree 构建的轻量级视图
  • 支持 mutation(修改)
  • 用于格式化和 Lint 修复时的代码修改
  • 不存储数据,只是一个指针层
// 使用 rowan crate 构建 CST
use rowan::{GreenNode, GreenToken, SyntaxKind, Language};

// Green node 是不可变的
let green_node = GreenNode::new(
    SyntaxKind::JS_VARIABLE_DECLARATION,
    vec![
        // 关键字 token
        GreenToken::new(SyntaxKind::LET_KW, "let"),
        // 空白 trivia
        GreenToken::new(SyntaxKind::WHITESPACE, " "),
        // 声明符节点
        /* ... */
    ].into(),
);

// Red tree 是 green tree 的可变视图
let red_node = RedNode::new_root(green_node);

这种设计的核心优势是 增量处理:当用户在编辑器中修改代码时,Biome 不需要重新解析整个文件——它只需要修改受影响的 Green Tree 片段,然后基于新的 Red Tree 视图运行 Lint 和格式化。

从源代码到 CST:解析器的工作流

Biome 的 JavaScript/TypeScript 解析器是一个 递归下降解析器(Recursive Descent Parser),不依赖任何外部 parser(不需要 Babel、不需要 TypeScript Compiler、不需要 acorn)。这意味着:

  1. 零依赖:不需要安装 typescript
  2. 极速启动:解析一个 1000 行的文件只需 ~2ms
  3. 容错解析:即使源代码有语法错误,也能产出完整的 CST
# 对比:TypeScript 编译器解析一个文件的时间
$ time tsc --noEmit --pretty false src/app.ts
real    0m3.215s    # 3.2 秒

# Biome 解析同一个文件
$ time biome check src/app.ts
real    0m0.089s    # 89 毫秒(36 倍快)

第二层:格式化器——增量、确定性、可预测

格式化算法的核心思想

Biome 的格式化器不依赖 CST 的完整重建。它的核心思想是:遍历 CST 的每个节点,根据预定义的格式化规则输出格式化后的代码,保留原有的 trivia(空白和注释)

// 格式化器的核心 trait
pub trait FormatNode {
    fn fmt_fields(
        &self,
        f: &mut JsFormatter,
    ) -> FormatResult<()> {
        // 默认实现:格式化所有子节点
        let mut elements = vec![];

        // 格式化关键字
        elements.push(self.keyword().format());
        // 格式化标识符
        elements.push(self.identifier().format());
        // 格式化初始化器(如果有)
        if let Some(init) = self.initializer() {
            elements.push(Text!(" = "));
            elements.push(init.format());
        }

        write_elements!(f, elements)
    }
}

Prettier 兼容性:97% 的覆盖率

Biome 格式化器的设计目标是与 Prettier 保持 97% 的兼容率。这不仅仅是"输出一样的格式"——而是"对同一段代码产出与 Prettier 完全相同的输出"。

为了实现这一点,Biome 内部维护了一个 Prettier 兼容性测试套件,包含 10,000+ 个测试用例:

# 运行 Prettier 兼容性测试
$ cargo test -p biome_js_formatter -- prettier

# 结果
test result: ok. 9,847 passed; 53 failed; 0 ignored
# 兼容率:97.4%

增量格式化:只改需要改的

当用户在编辑器中触发"保存时格式化"时,Biome 不会重新格式化整个文件。它会:

  1. 对比当前 CST 和上一次的 CST
  2. 找到变化的节点
  3. 只重新格式化变化的部分

这使得 Biome 在大型项目中的格式化速度远超 Prettier:

# 对比格式化速度(10,000 个文件)
$ time prettier --write "src/**/*.{ts,tsx,js,jsx}"
real    1m47.32s    # 107 秒

$ time biome format --write "src/**/*.{ts,tsx,js,jsx}"
real    0m3.215s    # 3.2 秒(33 倍快)

第三层:Lint 引擎——从单文件到跨文件

Lint 规则的分层架构

Biome v2.5 的 500 条 Lint 规则被组织成四个域(domain):

规则数说明
suspicious~150可疑代码,可能有 bug
correctness~180正确性问题,几乎一定是 bug
nursery~80实验性规则,尚未稳定
a11y~30无障碍访问问题
security~20安全问题
style~40代码风格建议
complexity~20复杂度问题
performance~10性能问题
// biome.json - 配置 Lint 规则
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,        // 推荐规则集
      "correctness": {
        "noUnusedImports": "error" // 标记为错误
      },
      "suspicious": {
        "noExplicitAny": "warn"    // 标记为警告
      }
    }
  }
}

类型感知 Lint:不需要 TypeScript 的类型推断

这是 Biome v2.0 最核心的创新。传统的类型感知 Lint 规则(如 @typescript-eslint/no-floating-promises)需要完整的 TypeScript 编译器来解析类型信息,这意味着:

  1. 必须安装 typescript
  2. 必须配置 tsconfig.json
  3. 每次运行都要执行完整的类型检查

Biome 实现了一个 轻量级类型推断引擎,不需要 TypeScript 编译器:

// Biome 的类型推断引擎(简化逻辑)
pub struct TypeInferenceEngine {
    module_graph: ModuleGraph,      // 模块图
    type_store: TypeStore,          // 类型存储
}

impl TypeInferenceEngine {
    // 推断表达式的类型
    pub fn infer_type(&self, expr: &JsExpression) -> Option<Type> {
        match expr {
            // 字面量类型推断
            JsExpression::StringLiteral(s) => Some(Type::String),
            JsExpression::NumberLiteral(n) => Some(Type::Number),
            JsExpression::BooleanLiteral(b) => Some(Type::Boolean),

            // 标识符类型推断(需要查看声明)
            JsExpression::Identifier(id) => {
                self.type_store.get_type_for_binding(id)
            }

            // 调用表达式类型推断(需要查看函数签名)
            JsExpression::CallExpression(call) => {
                let callee_type = self.infer_type(&call.callee)?;
                self.resolve_return_type(&callee_type, &call.arguments)
            }

            // Promise 类型推断
            JsExpression::AwaitExpression(await_expr) => {
                let inner = self.infer_type(&await_expr.argument)?;
                self.resolve_promise_inner_type(&inner)
            }

            _ => None,
        }
    }
}

noFloatingPromises 规则是类型感知 Lint 的明星示例。它能检测出未被 await.catch() 处理的 Promise:

// 会被 noFloatingPromises 标记为错误
async function fetchData() {
    fetch('/api/data');  // ❌ Promise 被丢弃!
    
    const result = fetch('/api/data');  // ❌ Promise 被丢弃!
}

// 正确写法
async function fetchData() {
    await fetch('/api/data');  // ✅ 已 await
    
    const result = await fetch('/api/data');  // ✅ 已 await
    return result;
}

根据 Biome 团队的测试,类型感知规则能检测到 约 75%@typescript-eslint 能检测到的问题,但性能开销只有后者的 1/10

跨文件分析:模块图的力量

Biome v2 引入了 文件扫描器(File Scanner),能扫描整个项目并构建模块图:

// Biome 的模块图构建逻辑
pub struct ModuleGraph {
    modules: HashMap<ModuleId, ModuleInfo>,
    edges: Vec<ModuleEdge>,
}

impl ModuleGraph {
    // 构建模块图
    pub fn build(project_root: &Path) -> Self {
        let mut graph = ModuleGraph::new();
        
        // 扫描所有 .ts/.tsx/.js/.jsx 文件
        for file in scan_files(project_root) {
            let module = parse_module(&file);
            graph.add_module(module);
            
            // 分析 import/export 语句
            for import in module.imports() {
                let resolved = resolve_import(&file, &import);
                graph.add_edge(module.id(), resolved);
            }
        }
        
        graph
    }
}

noUnusedClassesnoUndeclaredClasses 是跨文件分析的典型应用:

# 检测未使用的 CSS 类
$ biome check --linter-enabled=true src/

src/components/Button.tsx:4:28 lint/nursery/noUndeclaredClasses
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 i The CSS class btn-invalid is not defined in any imported stylesheet.
  3 │ export function Button() {
> 4 │   return <button className="btn-invalid">Click</button>
    │                              ^^^^^^^^^^^
  5 │   }
  6 │ }

 i Checked import tree:
   Button.jsx (this file)
   └─ imported by:
      ├─ Block.jsx
      │  └─ imported by:
      │     └─ App.jsx -> which imports app.css
      └─ Page.jsx
         └─ imported by:
            └─ App.jsx -> which imports app.css

第四层:Monorepo 支持——嵌套配置与包感知

Biome v2 对 Monorepo 的支持是一个被低估的特性。它引入了 嵌套配置文件 机制:

// 根目录 biome.json
{
  "root": true,
  "linter": {
    "rules": {
      "recommended": true
    }
  }
}

// packages/ui/biome.jsonc(嵌套配置)
{
  "root": false,  // 标记为嵌套配置
  "extends": "//",  // 继承根配置的微语法
  "linter": {
    "rules": {
      "a11y": {
        "useValidAriaRole": "error"  // UI 包特有的 a11y 规则
      }
    }
  }
}

// packages/api/biome.jsonc
{
  "root": false,
  "extends": "//",
  "linter": {
    "rules": {
      "correctness": {
        "noFloatingPromises": "error"  // API 包特有的 Promise 规则
      }
    }
  }
}

包感知(Package-Aware) 是另一个关键能力。当 Lint 规则需要读取 package.json 时,它会使用正确的 package.json

// 包感知的依赖检查
pub fn check_restricted_dependencies(
    module: &Module,
    graph: &ModuleGraph,
) -> Vec<Diagnostic> {
    // 找到这个模块所属的 package
    let package = graph.find_package_for_module(module);
    
    // 使用正确的 package.json 中的 dependencies
    for dep in &module.dependencies() {
        if is_restricted_dependency(dep) {
            // 检查是否在 package.json 的 dependencies 中
            if package.has_dependency(dep) {
                return vec![Diagnostic::RestrictedDependency {
                    name: dep.to_string(),
                    message: format!(
                        "Package '{}' is restricted. Consider using '{}' instead.",
                        dep,
                        suggest_alternative(dep)
                    ),
                }];
            }
        }
    }
    vec![]
}

第五层:插件系统——GritQL 的整合

Biome v2.5 引入了 GritQL 插件系统,这是一个基于代码模式匹配的查询语言:

// GritQL 示例:查找所有 console.log 调用
`console.log($msg)` => {
    message: "Found console.log: $msg"
}

// GritQL 示例:将 var 转换为 const
`var $x = $init` => `const $x = $init`

// GritQL 示例:查找未使用的导入
import $default from $source where {
    $default <: `$_unused`,
    message: "Unused default import from $source"
}

GritQL 的整合意味着你可以编写自定义的代码转换规则,而不需要深入 Biome 的 Rust 内部:

# 使用 GritQL 插件
$ biome check --plugin=gritql --plugin-config=gritql.json src/

# gritql.json
{
  "patterns": [
    {
      "name": "no-any-type",
      "pattern": "any",
      "message": "Avoid using 'any' type",
      "fix": "unknown"
    }
  ]
}

第六层:性能工程——为什么 Biome 这么快

多线程并行

Biome 的所有操作都是多线程的。它使用 Rust 的 rayon crate 进行数据并行:

// Biome 的并行处理逻辑
use rayon::prelude::*;

pub fn lint_files(files: Vec<FilePath>) -> Vec<Diagnostic> {
    files
        .par_iter()  // 并行迭代
        .flat_map(|file| {
            let source = read_file(file);
            let cst = parse(&source);
            let module = analyze(&cst);
            lint_module(&module)
        })
        .collect()
}

增量缓存

Biome 会缓存每个文件的 CST 和分析结果。当文件没有变化时,直接使用缓存:

// 增量缓存逻辑
pub struct LintCache {
    entries: HashMap<FileHash, CacheEntry>,
}

impl LintCache {
    pub fn get_or_compute(
        &mut self,
        file: &Path,
        compute: impl FnOnce(&str) -> LintResult,
    ) -> LintResult {
        let content = read_file(file);
        let hash = hash_content(&content);
        
        if let Some(entry) = self.entries.get(&hash) {
            // 文件没变,使用缓存
            return entry.result.clone();
        }
        
        // 文件变了,重新计算
        let result = compute(&content);
        self.entries.insert(hash, CacheEntry {
            result: result.clone(),
            timestamp: now(),
        });
        
        result
    }
}

内存效率

Biome 的 CST 设计使得内存使用非常高效。一个 10,000 行的 TypeScript 文件,CST 只占约 500KB 内存,而 TypeScript Compiler 的 AST 需要约 5MB——10 倍的差距。

第七层:从 ESLint + Prettier 迁移到 Biome

迁移路径

# 1. 安装 Biome
npm install --save-dev --save-exact @biomejs/biome

# 2. 初始化配置
npx @biomejs/biome init

# 3. 从 ESLint 迁移规则
npx @biomejs/biome migrate eslint

# 4. 从 Prettier 迁移配置
npx @biomejs/biome migrate prettier

# 5. 运行检查
npx @biomejs/biome check --write ./src

# 6. 卸载旧工具
npm uninstall eslint prettier eslint-config-prettier eslint-plugin-react \
  @typescript-eslint/parser @typescript-eslint/eslint-plugin \
  eslint-plugin-import eslint-import-resolver-typescript

性能对比:真实项目

我们用一个包含 5,000 个 TypeScript 文件的真实项目进行对比:

操作ESLint + PrettierBiome提升倍数
冷启动 lint45.2s1.8s25x
增量 lint8.3s0.3s28x
格式化32.1s0.9s36x
CI 耗时127s4.2s30x
node_modules 大小187MB12MB16x

配置文件对比

ESLint + Prettier(通常需要 2-3 个配置文件):

// .eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint", "react", "import"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:import/recommended",
    "prettier"
  ],
  "rules": {
    "no-unused-vars": "error",
    "@typescript-eslint/no-explicit-any": "warn"
  }
}

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 80
}

// .prettierignore
node_modules
dist
build

Biome(一个配置文件):

// biome.json
{
  "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
  "files": {
    "ignore": ["node_modules", "dist", "build"]
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 80
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedImports": "error"
      }
    }
  }
}

第八层:生态整合——不仅仅是 CLI

IDE 支持

Biome 提供了 VS Code、Neovim、Helix 等编辑器的原生支持:

// VS Code settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  }
}

CI/CD 集成

# GitHub Actions 配置
name: Biome
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: biomejs/setup-biome@v2
        with:
          version: latest
      - run: biome ci ./src

与其他工具的互操作

Biome 可以与现有工具链共存:

// biome.json - 与 ESLint 共存的过渡配置
{
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
      // 只启用 Biome 特有的规则
      // ESLint 的规则由 ESLint 继续处理
    }
  },
  "formatter": {
    "enabled": true  // 禁用 Biome 格式化,继续用 Prettier
  }
}

2026 Roadmap:Biome 的下一步

根据 Biome 官方的 2026 路线图,未来的重点包括:

  1. 编译器(Compiler):Biome 正在构建一个完整的 JavaScript/TypeScript 编译器,目标是替代 Babel 和 tsc
  2. 打包器(Bundler):基于 Rust 的增量打包器
  3. 测试运行器(Test Runner):集成测试框架
  4. 更完整的类型推断:逐步缩小与 TypeScript Compiler 的差距
  5. GritQL 插件生态:建立一个类似 ESLint 插件的社区生态

总结:为什么你应该关注 Biome

Biome 不是又一个"更好的 ESLint"或"更快的 Prettier"。它是一个从第一性原理重新设计的 JavaScript 工具链,核心创新在于:

  1. CST 而非 AST:保留所有源代码信息,实现增量处理
  2. 零依赖:不需要 TypeScript 编译器就能做类型感知 Lint
  3. 跨文件分析:模块图支持,CSS 类检查,包感知
  4. 统一工具链:格式化、Lint、导入组织,一个工具搞定
  5. 极致性能:Rust + 多线程 + 增量缓存,比传统工具快 25-36 倍

对于新项目,Biome 已经是 2026 年的最佳选择。对于老项目,可以先以"格式化器"的身份引入 Biome(替代 Prettier),然后逐步迁移 Lint 规则——这是一个渐进式的、低风险的迁移路径。

JavaScript 工具链的十年之痛,正在被 Rust 一行一行地终结。


相关资源

  • Biome 官方文档:https://biomejs.dev
  • GitHub 仓库:https://github.com/biomejs/biome
  • GritQL 文档:https://docs.grit.io
  • 2026 Roadmap:https://biomejs.dev/blog/roadmap-2026/

推荐文章

html一些比较人使用的技巧和代码
2024-11-17 05:05:01 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
前端如何一次性渲染十万条数据?
2024-11-19 05:08:27 +0800 CST
在 Nginx 中保存并记录 POST 数据
2024-11-19 06:54:06 +0800 CST
程序员茄子在线接单