Note — Introduced in Bun v0.1.11.
Bun's runtime can be extended to support additional file types using plugins. Plugins can intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to extend Bun's runtime with loaders for additional file types.
Usage
A plugin is defined as simple JavaScript object containing a name
property and a setup
function. Register a plugin with Bun using the plugin
function.
import { plugin } from "bun";
plugin({
name: "YAML loader",
setup(build) {
// implementation
},
});
To consume this plugin, add this file to the preload
option in your bunfig.toml
. Bun automatically loads the files/modules specified in preload
before running a file.
preload = ["./yamlPlugin.ts"]
Usage without preload
Third party plugins
By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object.
import { plugin } from "bun";
import fooPlugin from "bun-plugin-foo";
plugin(
fooPlugin({
// configuration
}),
);
// application code
Bun's plugin API is based on esbuild. Only a subset of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the official MDX loader:
import { plugin } from "bun";
import mdx from "@mdx-js/esbuild";
plugin(mdx());
import { renderToStaticMarkup } from "react-dom/server";
import Foo from "./bar.mdx";
console.log(renderToStaticMarkup(<Foo />));
Loaders
Plugins are primarily used to extend Bun with loaders for additional file types. Let's look at a simple plugin that implements a loader for .yaml
files.
import { plugin } from "bun";
plugin({
name: "YAML",
async setup(build) {
const { load } = await import("js-yaml");
const { readFileSync } = await import("fs");
// when a .yaml file is imported...
build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => {
// read and parse the file
const text = readFileSync(args.path, "utf8");
const exports = load(text) as Record<string, any>;
// and returns it as a module
return {
exports,
loader: "object", // special loader for JS objects
};
});
},
});
With this plugin, data can be directly imported from .yaml
files.
import "./yamlPlugin.ts"
import {name, releaseYear} from "./data.yml"
console.log(name, releaseYear);
name: Fast X
releaseYear: 2023
Note that the returned object has a loader
property. This tells Bun which of its internal loaders should be used to handle the result. Even though we're implementing a loader for .yaml
, the result must still be understandable by one of Bun's built-in loaders. It's loaders all the way down.
In this case we're using "object"
—a special loader (intended for use by plugins) that converts a plain JavaScript object to an equivalent ES module. Any of Bun's built-in loaders are supported; these same loaders are used by Bun internally for handling files of various extensions.
Loader | Extensions | Output |
---|---|---|
js | .js .mjs .cjs | Transpile to JavaScript files |
jsx | .jsx | Transform JSX then transpile |
ts | .ts .mts cts | Transform TypeScript then transpile |
tsx | .tsx | Transform TypeScript, JSX, then transpile |
toml | .toml | Parse using Bun's built-in TOML parser |
json | .json | Parse using Bun's built-in JSON parser |
object | — | A special loader intended for plugins that converts a plain JavaScript object to an equivalent ES module. Each key in the object corresponds to a named export. |
Loading a YAML file is useful, but plugins support more than just data loading. Let's look at a plugin that lets Bun import *.svelte
files.
import { plugin } from "bun";
await plugin({
name: "svelte loader",
async setup(build) {
const { compile } = await import("svelte/compiler");
const { readFileSync } = await import("fs");
// when a .svelte file is imported...
build.onLoad({ filter: /\.svelte$/ }, ({ path }) => {
// read and compile it with the Svelte compiler
const file = readFileSync(path, "utf8");
const contents = compile(file, {
filename: path,
generate: "ssr",
}).js.code;
// and return the compiled source code as "js"
return {
contents,
loader: "js",
};
});
},
});
Note: in a production implementation, you'd want to cache the compiled output and include additional error handling.
The object returned from build.onLoad
contains the compiled source code in contents
and specifies "js"
as its loader. That tells Bun to consider the returned contents
to be a JavaScript module and transpile it using Bun's built-in js
loader.
With this plugin, Svelte components can now be directly imported and consumed.
import "./sveltePlugin.ts";
import MySvelteComponent from "./component.svelte";
console.log(mySvelteComponent.render());
Reference
namespace Bun {
function plugin(plugin: { name: string; setup: (build: PluginBuilder) => void }): void;
}
type PluginBuilder = {
onLoad: (
args: { filter: RegExp; namespace?: string },
callback: (args: { path: string }) => {
loader?: "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "object";
contents?: string;
exports?: Record<string, any>;
},
) => void;
};
The onLoad
method optionally accepts a namespace
in addition to the filter
regex. This namespace will be be used to prefix the import in transpiled code; for instance, a loader with a filter: /\.yaml$/
and namespace: "yaml:"
will transform an import from ./myfile.yaml
into yaml:./myfile.yaml
.