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
transformSyncfor small, occasional transforms or when blocking the event loop is acceptable. This avoids the thread-pool scheduling overhead and executes immediately insrc/transpiler.zigstarting at line 1495. -
Use
transformfor 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.
transformSyncruns on the current thread for immediate, low-overhead transformation, whiletransformdispatches to Bun's thread-pool for concurrent processing.scanandscanImportsextract 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 indocs/runtime/transpiler.mdxand TypeScript definitions inpackages/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →