Blog

Getting Started with Frontend Tool Development: Understanding Code Structure with OXC Parser

Learn AST (Abstract Syntax Tree) from scratch, use OXC Parser to parse JavaScript code, and build your first code analysis tool.

LibDoc Team March 6, 2026 OXC Series 69 min read
#oxc #parser #ast #code parsing #rust

Getting Started with Frontend Tool Development: Understanding Code Structure with OXC Parser

Have you ever wondered: How does ESLint find code issues? How does Babel convert ES6 to ES5? How does Prettier reformat code?

The answer points to the same core technology — AST (Abstract Syntax Tree). And Parser is the tool that converts code into AST.

Today, we’ll use OXC Parser to unveil this magic.

What is AST?

Understanding with a Simple Analogy

Imagine you have a treasure map that says: “Start from the big tree, walk 100 steps north, then 50 steps east, dig.”

The computer needs to convert this sentence into a structure it can understand:

Instruction
├── Start: Big tree
├── Steps
│   ├── Step 1: Walk 100 steps north
│   └── Step 2: Walk 50 steps east
└── Action: Dig

AST is the “tree map” of code. It converts code strings into a tree, where each node represents an element in the code.

A Simple Example

Look at this code:

const greeting = "Hello, World!";

Its AST structure looks roughly like this:

Program
└── VariableDeclaration
    ├── kind: "const"
    └── declarations
        └── VariableDeclarator
            ├── id: Identifier
            │   └── name: "greeting"
            └── init: Literal
                └── value: "Hello, World!"

Why Do Frontend Tools Need AST?

All tools that need to “understand” code require AST:

ToolPurposeAST’s Role
ESLintCode lintingTraverse AST to find problem patterns
BabelCode transformationModify AST nodes, generate new code
PrettierCode formattingReformat based on AST
TypeScriptType checkingPerform type inference on AST
WebpackCode bundlingAnalyze dependencies in AST

Parser’s Role

Parser’s workflow:

Code string → Lexical Analysis → Syntax Analysis → AST

Browsers Use It Too

When a browser executes JavaScript:

  1. Parsing Phase: Convert code string to AST
  2. Compilation Phase: Compile AST to bytecode
  3. Execution Phase: Run bytecode

Babel and ESLint Use It Too

  • @babel/parser: Babel’s parser
  • @typescript-eslint/parser: ESLint’s TypeScript parser
  • @oxc/parser: OXC’s high-performance parser

OXC Parser’s features:

  • Extreme Speed: 20-50x faster than @babel/parser
  • Complete Support: JavaScript, TypeScript, JSX
  • Good Compatibility: Output format compatible with ESTree

Practical: Parsing Code with Parser

Let’s try it hands-on!

Installing OXC Parser

# Project installation
npm install @oxc-parser

# Or using pnpm
pnpm add @oxc-parser

Simplest Example

Create 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));

Run:

node parse-demo.js

Output:

{
  "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!"
          }
        }
      ]
    }
  ]
}

Asynchronous Parsing

For large files, asynchronous methods are recommended:

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

const code = `// Lots of code...`;

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

Understanding AST Output Step by Step

Let’s analyze a slightly more complex example:

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

Parsed AST structure:

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

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

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

Top-level Structure: Program

The root node of AST is Program:

{
  type: "Program",
  body: [...],  // Contains all top-level statements
  sourceType: "script"
}

Function Declaration: FunctionDeclaration

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "add"          // Function name
  },
  params: [             // Parameter list
    { type: "Identifier", name: "a" },
    { type: "Identifier", name: "b" }
  ],
  body: {               // Function body
    type: "BlockStatement",
    body: [...]
  }
}

Return Statement: ReturnStatement

{
  type: "ReturnStatement",
  argument: {           // Return value expression
    type: "BinaryExpression",
    operator: "+",
    left: { type: "Identifier", name: "a" },
    right: { type: "Identifier", name: "b" }
  }
}

Common Node Types

Node TypeDescriptionExample
IdentifierIdentifier (variable name, function name)foo, bar
LiteralLiteral value123, "hello", true
VariableDeclarationVariable declarationconst x = 1;
FunctionDeclarationFunction declarationfunction foo() {}
CallExpressionFunction callfoo()
BinaryExpressionBinary operationa + b
MemberExpressionMember accessobj.prop
ArrowFunctionExpressionArrow function(x) => x * 2

Practical Mini-Project: Counting Functions in Code

Let’s use Parser to create a practical tool — counting how many functions are defined in code.

Creating the Statistics Script

Create 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;

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

    // Check node type
    if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
      functionCount++;
    }
    if (node.type === "ArrowFunctionExpression") {
      arrowFunctionCount++;
    }

    // Recursively traverse all child nodes
    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,
  };
}

// Test
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("Function statistics:", result);

Run result:

Function statistics: {
  regularFunctions: 2,
  arrowFunctions: 2,
  total: 4
}

More Practical Version: Analyzing Files

// 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;
}

// Usage example
const stats = analyzeFile("./src/index.js");
console.log("File analysis result:", stats);

Parsing TypeScript

OXC Parser has excellent TypeScript support:

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",
  // Preserve type information
  astType: "ts",
});

console.log(ast);

Parsing 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",
});

Performance Advantage

Let’s compare OXC Parser and @babel/parser performance.

Test Code

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

// Generate large test code
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 lines of code

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

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

Test Results

OXC Parser: 45ms
Babel Parser: 890ms

OXC Parser is about 20x faster than Babel Parser!

Performance Comparison Table

Code Size@babel/parser@oxc/parserSpeedup
100 lines15ms1ms15x
1000 lines89ms4ms22x
10000 lines890ms45ms20x

Real-World Application Scenarios

1. Custom ESLint Rules

// Detect if console.log is used
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. Code Statistics Tool

// Calculate code complexity
function calculateComplexity(code) {
  const ast = parseSync(code, { lang: "js" });
  let complexity = 1; // Base complexity

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

    // Structures that increase complexity
    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. Code Generation Tool

// Extract all exported function names
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;
}

Summary

This article covered OXC Parser’s core concepts and usage:

ContentDescription
ASTTree representation of code, foundation of frontend tools
ParserConverts code string to AST
Node TypesIdentifier, Literal, FunctionDeclaration, etc.
Traversing ASTRecursively traverse all nodes for analysis

Parser’s core value: Making code understandable and manipulable by programs.

Next Steps

Want to learn more about Parser’s API options? Visit OXC Documentation - Parser Section

In the next article, we’ll learn about OXC Transformer — using it to compile TypeScript to JavaScript!


💡 Related Reading: