博客

前端工具开发入门:用 OXC Parser 理解代码结构

从零开始学习 AST(抽象语法树),使用 OXC Parser 解析 JavaScript 代码,构建你的第一个代码分析工具。

LibDoc Team 2026年3月6日 OXC 专栏 57 分钟阅读
#oxc #parser #ast #代码解析 #rust

前端工具开发入门:用 OXC Parser 理解代码结构

你是否好奇过:ESLint 是怎么发现代码问题的?Babel 是怎么把 ES6 转成 ES5 的?Prettier 是怎么重新排版代码的?

答案都指向同一个核心技术 —— AST(抽象语法树)。而 Parser(解析器)就是将代码转换为 AST 的工具。

今天,我们将用 OXC Parser 来揭开这个魔法的面纱。

什么是 AST?

用通俗的比喻理解

想象你有一张藏宝图,上面写着:“从大树出发,向北走 100 步,再向东走 50 步,挖掘”。

计算机需要把这句话变成它可以理解的结构:

指令
├── 起点: 大树
├── 步骤
│   ├── 步骤1: 向北走 100 步
│   └── 步骤2: 向东走 50 步
└── 动作: 挖掘

AST 就是代码的”树形地图”。它把代码字符串转换成一棵树,每个节点代表代码中的一个元素。

一个简单的例子

看这段代码:

const greeting = "Hello, World!";

它的 AST 结构大概是这样的:

Program (程序)
└── VariableDeclaration (变量声明)
    ├── kind: "const"
    └── declarations
        └── VariableDeclarator
            ├── id: Identifier (标识符)
            │   └── name: "greeting"
            └── init: Literal (字面量)
                └── value: "Hello, World!"

为什么前端工具需要 AST?

所有需要”理解”代码的工具都需要 AST:

工具用途AST 的作用
ESLint代码检查遍历 AST 找出问题模式
Babel代码转换修改 AST 节点,生成新代码
Prettier代码格式化根据 AST 重新排版
TypeScript类型检查在 AST 上进行类型推断
Webpack代码打包分析 AST 中的依赖关系

Parser 的作用

Parser(解析器)的工作流程:

代码字符串 → 词法分析 → 语法分析 → AST

浏览器也在用

当浏览器执行 JavaScript 时:

  1. 解析阶段:把代码字符串转换成 AST
  2. 编译阶段:把 AST 编译成字节码
  3. 执行阶段:运行字节码

Babel、ESLint 也在用

  • @babel/parser:Babel 的解析器
  • @typescript-eslint/parser:ESLint 的 TypeScript 解析器
  • @oxc/parser:OXC 的高性能解析器

OXC Parser 的特点:

  • 极致速度:比 @babel/parser 快 20-50 倍
  • 完整支持:JavaScript、TypeScript、JSX
  • 兼容性好:输出格式与 ESTree 兼容

实战:用 Parser 解析一段代码

让我们动手试试!

安装 OXC Parser

# 项目内安装
npm install @oxc-parser

# 或者使用 pnpm
pnpm add @oxc-parser

最简单的例子

创建 parse-demo.js

// parse-demo.js
import { parseSync } from "@oxc-parser";

const code = `const greeting = "Hello, OXC!";`;

const ast = parseSync(code, {
  lang: "js",
});

console.log(JSON.stringify(ast, null, 2));

运行:

node parse-demo.js

输出:

{
  "type": "Program",
  "start": 0,
  "end": 32,
  "body": [
    {
      "type": "VariableDeclaration",
      "kind": "const",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "greeting"
          },
          "init": {
            "type": "Literal",
            "value": "Hello, OXC!"
          }
        }
      ]
    }
  ]
}

异步解析

对于大文件,推荐使用异步方法:

import { parseAsync } from "@oxc-parser";

const code = `// 大量代码...`;

const ast = await parseAsync(code, {
  lang: "js",
});

一步一步理解 AST 输出

让我们分析一个稍微复杂的例子:

function add(a, b) {
  return a + b;
}

解析后的 AST 结构:

import { parseSync } from "@oxc-parser";

const code = `
function add(a, b) {
  return a + b;
}
`;

const ast = parseSync(code, { lang: "js" });

顶层结构:Program

AST 的根节点是 Program

{
  type: "Program",
  body: [...],  // 包含所有顶层语句
  sourceType: "script"
}

函数声明:FunctionDeclaration

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "add"          // 函数名
  },
  params: [             // 参数列表
    { type: "Identifier", name: "a" },
    { type: "Identifier", name: "b" }
  ],
  body: {               // 函数体
    type: "BlockStatement",
    body: [...]
  }
}

返回语句:ReturnStatement

{
  type: "ReturnStatement",
  argument: {           // 返回值表达式
    type: "BinaryExpression",
    operator: "+",
    left: { type: "Identifier", name: "a" },
    right: { type: "Identifier", name: "b" }
  }
}

常见节点类型

节点类型说明示例
Identifier标识符(变量名、函数名)foo, bar
Literal字面量123, "hello", true
VariableDeclaration变量声明const x = 1;
FunctionDeclaration函数声明function foo() {}
CallExpression函数调用foo()
BinaryExpression二元运算a + b
MemberExpression成员访问obj.prop
ArrowFunctionExpression箭头函数(x) => x * 2

实用小项目:统计代码中的函数数量

让我们用 Parser 做一个实用工具 —— 统计代码中定义了多少个函数。

创建统计脚本

创建 count-functions.js

// count-functions.js
import { parseSync } from "@oxc-parser";
import fs from "fs";

function countFunctions(code) {
  const ast = parseSync(code, { lang: "js" });

  let functionCount = 0;
  let arrowFunctionCount = 0;

  // 递归遍历 AST
  function traverse(node) {
    if (!node || typeof node !== "object") return;

    // 检查节点类型
    if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
      functionCount++;
    }
    if (node.type === "ArrowFunctionExpression") {
      arrowFunctionCount++;
    }

    // 递归遍历所有子节点
    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);

  return {
    regularFunctions: functionCount,
    arrowFunctions: arrowFunctionCount,
    total: functionCount + arrowFunctionCount,
  };
}

// 测试
const testCode = `
function add(a, b) {
  return a + b;
}

const multiply = function(a, b) {
  return a * b;
};

const subtract = (a, b) => a - b;

const numbers = [1, 2, 3].map(n => n * 2);
`;

const result = countFunctions(testCode);
console.log("函数统计结果:", result);

运行结果:

函数统计结果: {
  regularFunctions: 2,
  arrowFunctions: 2,
  total: 4
}

更实用的版本:分析文件

// analyze-file.js
import { parseSync } from "@oxc-parser";
import fs from "fs";

function analyzeFile(filePath) {
  const code = fs.readFileSync(filePath, "utf-8");
  const ast = parseSync(code, {
    lang: filePath.endsWith(".ts") ? "ts" : "js",
  });

  const stats = {
    functions: 0,
    classes: 0,
    imports: 0,
    exports: 0,
    variables: 0,
  };

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    switch (node.type) {
      case "FunctionDeclaration":
      case "FunctionExpression":
      case "ArrowFunctionExpression":
        stats.functions++;
        break;
      case "ClassDeclaration":
        stats.classes++;
        break;
      case "ImportDeclaration":
        stats.imports++;
        break;
      case "ExportNamedDeclaration":
      case "ExportDefaultDeclaration":
        stats.exports++;
        break;
      case "VariableDeclaration":
        stats.variables += node.declarations.length;
        break;
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return stats;
}

// 使用示例
const stats = analyzeFile("./src/index.js");
console.log("文件分析结果:", stats);

解析 TypeScript

OXC Parser 对 TypeScript 有很好的支持:

import { parseSync } from "@oxc-parser";

const tsCode = `
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return \`Hello, \${user.name}!\`;
}
`;

const ast = parseSync(tsCode, {
  lang: "ts",
  // 保留类型信息
  astType: "ts",
});

console.log(ast);

解析 JSX / TSX

import { parseSync } from "@oxc-parser";

const jsxCode = `
function Button({ onClick, children }) {
  return (
    <button className="btn" onClick={onClick}>
      {children}
    </button>
  );
}
`;

const ast = parseSync(jsxCode, {
  lang: "jsx",
});

性能优势

让我们对比一下 OXC Parser 和 @babel/parser 的性能。

测试代码

// benchmark.js
import { parseSync as oxcParse } from "@oxc-parser";
import { parse as babelParse } from "@babel/parser";

// 生成大量测试代码
function generateCode(lines) {
  let code = "";
  for (let i = 0; i < lines; i++) {
    code += `const variable${i} = ${i};\n`;
    code += `function function${i}() { return variable${i} * 2; }\n`;
  }
  return code;
}

const largeCode = generateCode(1000); // 2000 行代码

// 测试 OXC Parser
console.time("OXC Parser");
for (let i = 0; i < 10; i++) {
  oxcParse(largeCode, { lang: "js" });
}
console.timeEnd("OXC Parser");

// 测试 Babel Parser
console.time("Babel Parser");
for (let i = 0; i < 10; i++) {
  babelParse(largeCode, {
    sourceType: "module",
  });
}
console.timeEnd("Babel Parser");

测试结果

OXC Parser: 45ms
Babel Parser: 890ms

OXC Parser 比 Babel Parser 快约 20 倍!

性能对比表

代码规模@babel/parser@oxc/parser提升倍数
100 行15ms1ms15x
1000 行89ms4ms22x
10000 行890ms45ms20x

实际应用场景

1. 自定义 ESLint 规则

// 检测是否使用了 console.log
function noConsoleLog(code) {
  const ast = parseSync(code, { lang: "js" });
  const violations = [];

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    if (
      node.type === "CallExpression" &&
      node.callee?.object?.name === "console" &&
      node.callee?.property?.name === "log"
    ) {
      violations.push({
        line: node.loc?.start?.line,
        message: "Avoid using console.log",
      });
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return violations;
}

2. 代码统计工具

// 统计代码复杂度
function calculateComplexity(code) {
  const ast = parseSync(code, { lang: "js" });
  let complexity = 1; // 基础复杂度

  function traverse(node) {
    if (!node || typeof node !== "object") return;

    // 增加复杂度的结构
    if (["IfStatement", "ForStatement", "WhileStatement", "SwitchCase", "CatchClause"].includes(node.type)) {
      complexity++;
    }
    if (node.type === "ConditionalExpression") {
      complexity++;
    }
    if (node.type === "LogicalExpression") {
      complexity++;
    }

    for (const key of Object.keys(node)) {
      const child = node[key];
      if (Array.isArray(child)) {
        child.forEach(traverse);
      } else if (child && typeof child === "object") {
        traverse(child);
      }
    }
  }

  traverse(ast);
  return complexity;
}

3. 代码生成工具

// 提取所有导出的函数名
function extractExports(code) {
  const ast = parseSync(code, { lang: "js" });
  const exports = [];

  for (const node of ast.body) {
    if (node.type === "ExportNamedDeclaration") {
      if (node.declaration?.type === "FunctionDeclaration") {
        exports.push(node.declaration.id.name);
      }
    }
    if (node.type === "ExportDefaultDeclaration") {
      if (node.declaration.type === "Identifier") {
        exports.push(`default: ${node.declaration.name}`);
      }
    }
  }

  return exports;
}

总结

本文介绍了 OXC Parser 的核心概念和用法:

内容说明
AST代码的树形表示,前端工具的基础
Parser将代码字符串转换为 AST
节点类型Identifier、Literal、FunctionDeclaration 等
遍历 AST递归遍历所有节点进行分析

Parser 的核心价值:让代码变得可以被程序”理解”和”操作”。

下一步

想了解更多 Parser 的 API 选项?访问 OXC 中文文档 - Parser 章节

下一篇文章,我们将学习 OXC Transformer —— 用它将 TypeScript 编译成 JavaScript!


💡 相关阅读