Bun exposes its internal transpiler via the Bun.Transpiler
class. To create an instance of Bun's transpiler:
const transpiler = new Bun.Transpiler({
loader: "tsx", // "js | "jsx" | "ts" | "tsx"
});
.transformSync()
Transpile code synchronously with the .transformSync()
method. Modules are not resolved and the code is not executed. The result is a string of vanilla JavaScript code.
const transpiler = new Bun.Transpiler({
loader: 'tsx',
});
const code = `
import * as whatever from "./whatever.ts"
export function Home(props: {title: string}){
return <p>{props.title}</p>;
}`;
const result = transpiler.transformSync(code);
import { __require as require } from "bun:wrap";
import * as JSX from "react/jsx-dev-runtime";
var jsx = require(JSX).jsxDEV;
export default jsx(
"div",
{
children: "hi!",
},
undefined,
false,
undefined,
this,
);
To override the default loader specified in the new Bun.Transpiler()
constructor, pass a second argument to .transformSync()
.
transpiler.transformSync("<div>hi!</div>", "tsx");
Nitty gritty
.transform()
The transform()
method is an async version of .transformSync()
that returns a Promise<string>
.
const transpiler = new Bun.Transpiler({ loader: "jsx" });
const result = await transpiler.transform("<div>hi!</div>");
console.log(result);
Unless you're transpiling many large files, you should probably use Bun.Transpiler.transformSync
. The cost of the threadpool will often take longer than actually transpiling code.
await transpiler.transform("<div>hi!</div>", "tsx");
Nitty gritty
.scan()
The Transpiler
instance can also scan some source code and return a list of its imports and exports, plus additional metadata about each one. Type-only imports and exports are ignored.
const transpiler = new Bun.Transpiler({
loader: 'tsx',
});
const code = `
import React from 'react';
import type {ReactNode} from 'react';
const val = require('./cjs.js')
import('./loader');
export const name = "hello";
`;
const result = transpiler.scan(code);
{
"exports": [
"name"
],
"imports": [
{
"kind": "import-statement",
"path": "react"
},
{
"kind": "import-statement",
"path": "remix"
},
{
"kind": "dynamic-import",
"path": "./loader"
}
]
}
Each import in the imports
array has a path
and kind
. Bun categories imports into the following kinds:
import-statement
:import React from 'react'
require-call
:const val = require('./cjs.js')
require-resolve
:require.resolve('./cjs.js')
dynamic-import
:import('./loader')
import-rule
:@import 'foo.css'
url-token
:url('./foo.png')
.scanImports()
For performance-sensitive code, you can use the .scanImports()
method to get a list of imports. It's faster than .scan()
(especially for large files) but marginally less accurate due to some performance optimizations.
const transpiler = new Bun.Transpiler({
loader: 'tsx',
});
const code = `
import React from 'react';
import type {ReactNode} from 'react';
const val = require('./cjs.js')
import('./loader');
export const name = "hello";
`;
const result = transpiler.scanImports(code);
[
{
kind: "import-statement",
path: "react"
}, {
kind: "require-call",
path: "./cjs.js"
}, {
kind: "dynamic-import",
path: "./loader"
}
]
Reference
type Loader = "jsx" | "js" | "ts" | "tsx";
interface TranspilerOptions {
// Replace key with value. Value must be a JSON string.
// { "process.env.NODE_ENV": "\"production\"" }
define?: Record<string, string>,
// Default loader for this transpiler
loader?: Loader,
// Default platform to target
// This affects how import and/or require is used
target?: "browser" | "bun" | "node",
// Specify a tsconfig.json file as stringified JSON or an object
// Use this to set a custom JSX factory, fragment, or import source
// For example, if you want to use Preact instead of React. Or if you want to use Emotion.
tsconfig?: string | TSConfig,
// Replace imports with macros
macro?: MacroMap,
// Specify a set of exports to eliminate
// Or rename certain exports
exports?: {
eliminate?: string[];
replace?: Record<string, string>;
},
// Whether to remove unused imports from transpiled file
// Default: false
trimUnusedImports?: boolean,
// Whether to enable a set of JSX optimizations
// jsxOptimizationInline ...,
// Experimental whitespace minification
minifyWhitespace?: boolean,
// Whether to inline constant values
// Typically improves performance and decreases bundle size
// Default: true
inline?: boolean,
}
// Map import paths to macros
interface MacroMap {
// {
// "react-relay": {
// "graphql": "bun-macro-relay/bun-macro-relay.tsx"
// }
// }
[packagePath: string]: {
[importItemName: string]: string,
},
}
class Bun.Transpiler {
constructor(options: TranspilerOptions)
transform(code: string, loader?: Loader): Promise<string>
transformSync(code: string, loader?: Loader): string
scan(code: string): {exports: string[], imports: Import}
scanImports(code: string): Import[]
}
type Import = {
path: string,
kind:
// import foo from 'bar'; in JavaScript
| "import-statement"
// require("foo") in JavaScript
| "require-call"
// require.resolve("foo") in JavaScript
| "require-resolve"
// Dynamic import() in JavaScript
| "dynamic-import"
// @import() in CSS
| "import-rule"
// url() in CSS
| "url-token"
// The import was injected by Bun
| "internal"
// Entry point (not common)
| "entry-point"
}
const transpiler = new Bun.Transpiler({ loader: "jsx" });