Intro
What is Bun?
Installation
Quickstart
TypeScript
Templating
bun init
bun create
Runtime
bun run
File types
TypeScript
JSX
Environment variables
Bun APIs
Web APIs
Node.js compatibility
Single-file executable
Plugins
Watch mode
Module resolution
Auto-install
bunfig.toml
Debugger
Framework APISOON
Package manager
bun install
bun add
bun remove
bun update
bun publish
bun outdated
bun link
bun pm
Global cache
Workspaces
Lifecycle scripts
Filter
Lockfile
Scopes and registries
Overrides and resolutions
Patch dependencies
.npmrc support
Bundler
Bun.build
HTML & static sites
CSS
Fullstack Dev Server
Hot reloading
Loaders
Plugins
Macros
vs esbuild
Test runner
bun test
Writing tests
Watch mode
Lifecycle hooks
Mocks
Snapshots
Dates and times
DOM testing
Code coverage
Package runner
bunx
API
HTTP server
HTTP client
WebSockets
Workers
Binary data
Streams
SQL
S3 Object Storage
File I/O
import.meta
SQLite
FileSystemRouter
TCP sockets
UDP sockets
Globals
$ Shell
Child processes
HTMLRewriter
Hashing
Console
Cookie
FFI
C Compiler
Testing
Utils
Node-API
Glob
DNS
Semver
Color
Transpiler
Project
Roadmap
Benchmarking
Contributing
Building Windows
Bindgen
License
Search the docs...
/
Intro
What is Bun?
Installation
Quickstart
TypeScript
Templating
bun init
bun create
Runtime
bun run
File types
TypeScript
JSX
Environment variables
Bun APIs
Web APIs
Node.js compatibility
Single-file executable
Plugins
onStart
onResolve
onLoad
Watch mode
Module resolution
Auto-install
bunfig.toml
Debugger
Framework APISOON
Package manager
bun install
bun add
bun remove
bun update
bun publish
bun outdated
bun link
bun pm
Global cache
Workspaces
Lifecycle scripts
Filter
Lockfile
Scopes and registries
Overrides and resolutions
Patch dependencies
.npmrc support
Bundler
Bun.build
HTML & static sites
CSS
Fullstack Dev Server
Hot reloading
Loaders
Plugins
Macros
vs esbuild
Test runner
bun test
Writing tests
Watch mode
Lifecycle hooks
Mocks
Snapshots
Dates and times
DOM testing
Code coverage
Package runner
bunx
API
HTTP server
HTTP client
WebSockets
Workers
Binary data
Streams
SQL
S3 Object Storage
File I/O
import.meta
SQLite
FileSystemRouter
TCP sockets
UDP sockets
Globals
$ Shell
Child processes
HTMLRewriter
Hashing
Console
Cookie
FFI
C Compiler
Testing
Utils
Node-API
Glob
DNS
Semver
Color
Transpiler
Project
Roadmap
Benchmarking
Contributing
Building Windows
Bindgen
License
Bun provides a universal plugin API that can be used to extend both theruntime andbundler.
Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like.scss
or.yaml
. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location.
A plugin is defined as simple JavaScript object containing aname
property and asetup
function. Register a plugin with Bun using theplugin
function.
import { plugin,type BunPlugin }from"bun";const myPlugin:BunPlugin= { name:"Custom loader",setup(build) {// implementation },};plugin(myPlugin);
Plugins have to be loaded before any other code runs! To achieve this, use thepreload
option in yourbunfig.toml
. Bun automatically loads the files/modules specified inpreload
before running a file.
preload= ["./myPlugin.ts"]
To preload files beforebun test
:
[test]preload= ["./myPlugin.ts"]
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 fooPluginfrom"bun-plugin-foo";plugin(fooPlugin({// configuration }),);
Bun's plugin API is loosely based onesbuild. Onlya subset of the esbuild API is implemented, but some esbuild plugins "just work" in Bun, like the officialMDX loader:
import { plugin }from"bun";import mdxfrom"@mdx-js/esbuild";plugin(mdx());
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";awaitplugin({ name:"YAML",asyncsetup(build) {const { load }=awaitimport("js-yaml");// when a .yaml file is imported... build.onLoad({ filter:/\.(yaml|yml)$/ },async (args)=> {// read and parse the fileconst text=await Bun.file(args.path).text();const exports=load(text)asRecord<string,any>;// and returns it as a modulereturn { exports, loader:"object",// special loader for JS objects }; }); },});
Register this file inpreload
:
preload= ["./yamlPlugin.ts"]
Once the plugin is registered,.yaml
and.yml
files can be directly imported.
import datafrom"./data.yml"console.log(data);
name:Fast XreleaseYear:2023
Note that the returned object has aloader
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 built-in 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 kinds. The table below is a quick reference; refer toBundler > Loaders for complete documentation.
Loader | Extensions | Output |
---|---|---|
js | .mjs .cjs | Transpile to JavaScript files |
jsx | .js .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 |
napi | .node | Import a native Node.js addon |
wasm | .wasm | Import a native Node.js addon |
object | none | 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";awaitplugin({ name:"svelte loader",asyncsetup(build) {const { compile }=awaitimport("svelte/compiler");// when a .svelte file is imported... build.onLoad({ filter:/\.svelte$/ },async ({path })=> {// read and compile it with the Svelte compilerconst file=await Bun.file(path).text();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 frombuild.onLoad
contains the compiled source code incontents
and specifies"js"
as its loader. That tells Bun to consider the returnedcontents
to be a JavaScript module and transpile it using Bun's built-injs
loader.
With this plugin, Svelte components can now be directly imported and consumed.
import"./sveltePlugin.ts";import MySvelteComponentfrom"./component.svelte";console.log(MySvelteComponent.render());
This feature is currently only available at runtime withBun.plugin
and not yet supported in the bundler, but you can mimic the behavior usingonResolve
andonLoad
.
To create virtual modules at runtime, usebuilder.module(specifier, callback)
in thesetup
function of aBun.plugin
.
For example:
import { plugin }from"bun";plugin({ name:"my-virtual-module",setup(build) { build.module(// The specifier, which can be any string - except a built-in, such as "buffer""my-transpiled-virtual-module",// The callback to run when the module is imported or required for the first time ()=> {return { contents:"console.log('hello world!')", loader:"js", }; }, ); build.module("my-object-virtual-module", ()=> {return { exports: { foo:"bar", }, loader:"object", }; }); },});// Sometime later// All of these workimport"my-transpiled-virtual-module";require("my-transpiled-virtual-module");awaitimport("my-transpiled-virtual-module");require.resolve("my-transpiled-virtual-module");import { foo }from"my-object-virtual-module";const object=require("my-object-virtual-module");awaitimport("my-object-virtual-module");require.resolve("my-object-virtual-module");
You can also override existing modules withbuild.module
.
import { plugin }from"bun";build.module("my-object-virtual-module", ()=> {return { exports: { foo:"bar", }, loader:"object", };});require("my-object-virtual-module");// { foo: "bar" }awaitimport("my-object-virtual-module");// { foo: "bar" }build.module("my-object-virtual-module", ()=> {return { exports: { baz:"quix", }, loader:"object", };});require("my-object-virtual-module");// { baz: "quix" }awaitimport("my-object-virtual-module");// { baz: "quix" }
Plugins can read and write to thebuild config withbuild.config
.
await Bun.build({ entrypoints: ["./app.ts"], outdir:"./dist", sourcemap:"external", plugins: [ { name:"demo",setup(build) { console.log(build.config.sourcemap);// "external" build.config.minify=true;// enable minification// `plugins` is readonly console.log(`Number of plugins:${build.config.plugins.length}`); }, }, ],});
NOTE: Plugin lifecycle callbacks (onStart()
,onResolve()
, etc.) do not have the ability to modify thebuild.config
object in thesetup()
function. If you want to mutatebuild.config
, you must do so directly in thesetup()
function:
await Bun.build({ entrypoints: ["./app.ts"], outdir:"./dist", sourcemap:"external", plugins: [ { name:"demo",setup(build) {// ✅ good! modifying it directly in the setup() function build.config.minify=true; build.onStart(()=> {// 🚫 uh-oh! this won't work! build.config.minify=false; }); }, }, ],});
Plugins can register callbacks to be run at various points in the lifecycle of a bundle:
onStart()
: Run once the bundler has started a bundleonResolve()
: Run before a module is resolvedonLoad()
: Run before a module is loaded.A rough overview of the types (please refer to Bun'sbun.d.ts
for the full type definitions):
namespaceBun {functionplugin(plugin: { name:string;setup: (build:PluginBuilder)=>void; }):void;}typePluginBuilder= {onStart(callback: ()=>void):void;onResolve: (args: { filter:RegExp; namespace?:string },callback: (args: { path:string; importer:string })=> { path:string; namespace?:string; }|void, )=>void;onLoad: (args: { filter:RegExp; namespace?:string },callback: (args: { path:string })=> { loader?:Loader; contents?:string; exports?:Record<string,any>; }, )=>void; config:BuildConfig;};typeLoader="js"|"jsx"|"ts"|"tsx"|"css"|"json"|"toml"|"object";
onLoad
andonResolve
accept an optionalnamespace
string. What is a namespace?
Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with afilter: /\.yaml$/
andnamespace: "yaml:"
will transform an import from./myfile.yaml
intoyaml:./myfile.yaml
.
The default namespace is"file"
and it is not necessary to specify it, for instance:import myModule from "./my-module.ts"
is the same asimport myModule from "file:./my-module.ts"
.
Other common namespaces are:
"bun"
: for Bun-specific modules (e.g."bun:test"
,"bun:sqlite"
)"node"
: for Node.js modules (e.g."node:fs"
,"node:path"
)onStart
onStart(callback: ()=>void):Promise<void>|void;
Registers a callback to be run when the bundler starts a new bundle.
import { plugin }from"bun";plugin({ name:"onStart example",setup(build) { build.onStart(()=> { console.log("Bundle started!"); }); },});
The callback can return aPromise
. After the bundle process has initialized, the bundler waits until allonStart()
callbacks have completed before continuing.
For example:
const result=await Bun.build({ entrypoints: ["./app.ts"], outdir:"./dist", sourcemap:"external", plugins: [ { name:"Sleep for 10 seconds",setup(build) { build.onStart(async ()=> {await Bunlog.sleep(10_000); }); }, }, { name:"Log bundle time to a file",setup(build) { build.onStart(async ()=> {const now=Date.now();awaitBun.$`echo${now} > bundle-time.txt`; }); }, }, ],});
In the above example, Bun will wait until the firstonStart()
(sleeping for 10 seconds) has completed,as well as the secondonStart()
(writing the bundle time to a file).
Note thatonStart()
callbacks (like every other lifecycle callback) do not have the ability to modify thebuild.config
object. If you want to mutatebuild.config
, you must do so directly in thesetup()
function.
onResolve
onResolve( args: { filter:RegExp; namespace?: string }, callback: (args: { path:string; importer:string })=> { path: string; namespace?: string; }|void,):void;
To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun actually has to find and read that module. The "finding" part is known as "resolving" a module.
TheonResolve()
plugin lifecycle callback allows you to configure how a module is resolved.
The first argument toonResolve()
is an object with afilter
andnamespace
property. The filter is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to.
The second argument toonResolve()
is a callback which is run for each module import Bun finds that matches thefilter
andnamespace
defined in the first argument.
The callback receives as input thepath to the matching module. The callback can return anew path for the module. Bun will read the contents of thenew path and parse it as a module.
For example, redirecting all imports toimages/
to./public/images/
:
import { plugin }from"bun";plugin({ name:"onResolve example",setup(build) { build.onResolve({ filter:/.*/, namespace:"file" },args=> {if (args.path.startsWith("images/")) {return { path: args.path.replace("images/","./public/images/"), }; } }); },});
onLoad
onLoad( args: { filter:RegExp; namespace?: string }, callback: (args: { path:string, importer:string, namespace:string, kind:ImportKind })=> { loader?: Loader; contents?: string;exports?: Record<string, any>; },):void;
After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it.
TheonLoad()
plugin lifecycle callback allows you to modify thecontents of a module before it is read and parsed by Bun.
LikeonResolve()
, the first argument toonLoad()
allows you to filter which modules this invocation ofonLoad()
will apply to.
The second argument toonLoad()
is a callback which is run for each matching modulebefore Bun loads the contents of the module into memory.
This callback receives as input thepath to the matching module, theimporter of the module (the module that imported the module), thenamespace of the module, and thekind of the module.
The callback can return a newcontents
string for the module as well as a newloader
.
For example:
import { plugin }from"bun";plugin({ name:"env plugin",setup(build) { build.onLoad({ filter:/env/, namespace:"file" },args=> {return { contents:`export default${JSON.stringify(process.env)}`, loader:"js", }; }); },});
This plugin will transform all imports of the formimport env from "env"
into a JavaScript module that exports the current environment variables.