How to Use Bun.Transpiler for Runtime Code Transformation: A Complete Guide

Bun.Transpiler is a high-performance JavaScript API that transforms TypeScript and JSX into vanilla JavaScript at runtime using Bun's Zig-based transpiler engine, supporting both synchronous and asynchronous execution patterns.

The Bun.Transpiler class in the oven-sh/bun repository provides direct access to Bun's internal transpilation pipeline without invoking the full bundler. This runtime API allows developers to transform, scan, and analyze JavaScript or TypeScript source code on demand, leveraging the same high-performance Zig implementation that powers Bun's build system.

Architecture and Implementation

Bun.Transpiler is a thin JavaScript wrapper around the core Transpiler implementation written in Zig. The class constructs a transpiler instance with user-provided options—such as loader, target, and macros—and delegates all public methods to the underlying Zig engine located in src/transpiler.zig.

The core implementation handles entry-point resolution, parsing via js_parser, macro execution, printing via js_printer, and optional source-map generation. When you instantiate new Bun.Transpiler(opts), the JavaScript bridge creates a Zig Transpiler instance through Transpiler.init, establishing the configuration context for all subsequent operations.

Core Transformation Methods

Synchronous Transformation with transformSync

transformSync executes the transpilation pipeline on the current thread, making it ideal for small, occasional transforms where thread-pool overhead would be wasteful. The method accepts source code and an optional loader override, then immediately processes the input through parse → print → options.TransformResult according to the implementation in src/transpiler.zig.

import { readFileSync } from "fs";

const transpiler = new Bun.Transpiler({ loader: "tsx" });

const source = readFileSync("./example.tsx", "utf8");
const js = transpiler.transformSync(source);   // → plain JS string
console.log(js);

Asynchronous Transformation with transform

transform schedules work on Bun's internal thread-pool, enabling non-blocking execution for batch processing or hot-reloading scenarios. Each job runs on its own event loop and executes the same parse and print pipeline in isolation, allowing heavy workloads to scale across CPU cores. The thread-pool dispatch occurs through processResolveQueue as implemented in the Zig source.

const transpiler = new Bun.Transpiler({ loader: "jsx" });

(async () => {
  const js = await transpiler.transform(`
    const el = <h1>Hello, world!</h1>;
    export default el;
  `);
  console.log(js);
})();

Static Analysis with scan and scanImports

For scenarios requiring metadata without code emission, .scan() and .scanImports() walk the AST to collect import and export information. These methods use the same parser infrastructure as the transform pipeline but skip the printing phase, returning structured metadata about dependencies.

const transpiler = new Bun.Transpiler({ loader: "tsx" });

const code = `
  import React from "react";
  const data = require("./data.json");
  export const name = "Bun";
`;

const meta = transpiler.scan(code);
console.log(meta.exports); // ["name"]
console.log(meta.imports); // [{kind:"import-statement", path:"react"}, …]

For faster import-only analysis, .scanImports() provides a streamlined interface:

const imports = new Bun.Transpiler({ loader: "tsx" }).scanImports(`
  import foo from "./foo";
  const bar = require("./bar");
`);
console.log(imports);
// → [{kind:"import-statement", path:"./foo"}, {kind:"require-call", path:"./bar"}]

Advanced Runtime Transformation Patterns

Overriding Loaders Per-Call

While the transpiler instance maintains default options, individual calls can override the loader. This allows a single Bun.Transpiler instance to handle multiple file types without reconstruction.

const transpiler = new Bun.Transpiler({ loader: "js" });

const result = transpiler.transformSync("<div>Hello</div>", "tsx");
console.log(result);   // JSX compiled as TSX

Macro Support

When macros are configured, the transpiler spawns a separate JavaScript runtime on the worker thread. Macro state lives in a shared js_ast.Macro.MacroContext, enabling compile-time code generation and transformation.

const transpiler = new Bun.Transpiler({
  loader: "tsx",
  macro: {
    "my-macro": "./macros/my-macro.tsx"
  }
});

const result = await transpiler.transform(`
  import { hello } from "my-macro";
  console.log(hello);
`);

Performance Considerations: Sync vs Async

Choosing between synchronous and asynchronous transformation depends on workload characteristics:

  • Use transformSync for small, occasional transforms or when blocking the event loop is acceptable. This avoids the thread-pool scheduling overhead and executes immediately in src/transpiler.zig starting at line 1495.

  • Use transform for batch processing, hot-reloading servers, or when processing multiple files concurrently. The thread-pool utilization ensures the main thread remains non-blocking while Zig workers handle the heavy lifting across available cores.

Summary

  • Bun.Transpiler provides direct runtime access to Bun's Zig-based transpilation engine without invoking the full bundler.
  • transformSync runs on the current thread for immediate, low-overhead transformation, while transform dispatches to Bun's thread-pool for concurrent processing.
  • scan and scanImports extract import/export metadata without generating code, useful for dependency analysis and linting.
  • Macro support executes in isolated JavaScript runtimes on worker threads, managed through js_ast.Macro.MacroContext.
  • The implementation resides primarily in src/transpiler.zig, with user-facing documentation in docs/runtime/transpiler.mdx and TypeScript definitions in packages/bun-types/bun.d.ts.

Frequently Asked Questions

How does Bun.Transpiler differ from Bun's bundler?

Bun.Transpiler exposes the underlying transformation engine directly, allowing runtime code transformation without entry-point resolution, dependency graph building, or output bundling. While the bundler orchestrates full builds, the Transpiler class focuses on single-file transformation and static analysis as implemented in src/transpiler.zig.

Can I use Bun.Transpiler in a production server environment?

Yes, the asynchronous transform method is specifically designed for server use cases. By utilizing Bun's thread-pool through processResolveQueue, it prevents heavy transpilation tasks from blocking the main event loop, making it suitable for hot-reloading development servers or on-demand transformation in production APIs.

What file types does Bun.Transpiler support?

The Transpiler accepts any loader supported by Bun, including "js", "jsx", "ts", "tsx", and others defined in packages/bun-types/bun.d.ts. You can set a default loader in the constructor or override it per-call, allowing flexible handling of mixed file types within a single instance.

How are errors handled during transformation?

Errors thrown during parsing or transformation propagate from the Zig implementation to the JavaScript caller. The test suite in test/regression/issue/012039.test.ts specifically validates error handling behavior in transformSync, ensuring that syntax errors and invalid configurations produce actionable JavaScript exceptions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →