new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Bun · Bundler · all subjects

plugins & hooks

38 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

onLoad hook arguments

The onLoad hook receives arguments including: path (supported) and namespace (supported). The suffix and pluginData arguments are not supported in Bun.

plugins API in Bun.build()

Bun.build() supports plugins, but Bun's plugin API is a subset of esbuild's. Some esbuild plugins work with Bun without modification.

Plugin API esbuild compatibility

Bun's plugin API is designed to be esbuild-compatible. Bun does not support esbuild's entire plugin API surface but implements core functionality. Many third-party esbuild plugins work with Bun without modification.

Plugin object structure

A Bun plugin is defined as an object with a name property and a setup method that receives a builder object. The setup method is where plugin hooks are defined.

Builder hooks implemented in Bun

Bun implements the following builder hooks: onStart, onEnd, onResolve, and onLoad. Bun does not implement the esbuild hooks onDispose and resolve.

initialOptions in Bun plugins

The initialOptions object in Bun plugins is read-only and exposes only a subset of esbuild's options. Use config (in Bun's BuildConfig format) instead.

onResolve hook supported options

The onResolve hook supports the following options: filter (supported) and namespace (supported).

onResolve hook arguments

The onResolve hook receives arguments including: path (supported), importer (supported), namespace (supported), resolveDir (supported), and kind (supported). The pluginData argument is not supported in Bun.

onResolve hook return results

The onResolve hook can return results with the following supported properties: namespace, path, and external. The following properties are not supported: errors, pluginData, pluginName, sideEffects, suffix, warnings, watchDirs, and watchFiles.

onLoad hook supported options

The onLoad hook supports the following options: filter (supported) and namespace (supported).

onLoad hook return results

The onLoad hook can return results with the following supported properties: contents and loader. The following properties are not supported: errors, pluginData, pluginName, resolveDir, warnings, watchDirs, and watchFiles.

Plugins work with standalone executables

Plugins work with standalone executables to transform files during the build. Use them to compile YAML/TOML configs, inline SQL queries, generate type-safe API clients, or preprocess templates.

plugins option for custom bundling behavior

The plugins option accepts a list of plugins to use during bundling. The runtime and the bundler share Bun's plugin system.

optimizeImports works with plugins

Resolve and load plugins work with barrel optimization. Deferred submodules go through the plugin pipeline when they are eventually loaded.

Plugin configuration via CLI vs bunfig.toml

Plugins are supported through `Bun.build()`'s API or through `bunfig.toml` with the frontend dev server. Plugins are not supported through `bun build`'s CLI.

HTMLRewriter plugin example for lowercase tags

Example plugin using `HTMLRewriter` to preprocess HTML: ```ts await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", minify: true, plugins: [ { name: "lowercase-html-plugin", setup({ onLoad }) { const rewriter = new HTMLRewriter().on("*", { element(element) { element.tagName = element.tagName.toLowerCase(); }, text(element) { element.replace(element.text.toLowerCase()); }, }); onLoad({ filter: /\.html$/ }, async args => { const html = await Bun.file(args.path).text(); return { contents: rewriter.transform(html), loader: "html", }; }); }, }, ], }); ``` The bundler automatically scans the HTML for `<script>` tags, `<link rel="stylesheet">` tags, and other assets to bundle.

Plugin lifecycle hooks overview

Plugins register callbacks that run at various points in the bundle lifecycle: onStart() runs once the bundler has started a bundle; onResolve() runs before the bundler resolves a module; onLoad() runs before the bundler loads a module; onBeforeParse() runs zero-copy native addons in the parser thread before the bundler parses a file; onEnd() runs after the bundle is complete.

Plugin object structure and usage

A plugin is a JavaScript object with a name property and a setup function. The setup function receives a build object. Pass plugins to Bun.build() in the plugins array.

Namespace concept in plugins

Every module has a namespace. Namespaces prefix the import in transpiled code. For example, a loader with filter /\.yaml$/ and namespace 'yaml:' transforms an import from ./myfile.yaml into yaml:./myfile.yaml. The default namespace is 'file'. Common namespaces include 'bun' for Bun-specific modules and 'node' for Node.js modules.

onStart callback signature and behavior

onStart(callback: () => void | Promise<void>): void. Registers a callback that runs when the bundler starts a new bundle. The callback can return a Promise. The bundler waits until all onStart() callbacks have completed before continuing.

onStart cannot modify build.config

The onStart() callback and every other lifecycle callback cannot modify the build.config object. To mutate build.config, do so directly in the setup() function.

onResolve callback signature

onResolve(args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string } | void): void. The filter is a regular expression run on the import string. The callback receives the path and importer and can return a new path and optional namespace for the module.

onLoad callback signature

onLoad(args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; namespace: string; loader: Loader; defer: () => Promise<void> }) => { loader?: Loader; contents?: string; exports?: Record<string, any> }): void. The callback runs for each matching module before Bun loads its contents. It can return a new contents string, loader, or exports object.

onLoad defer function behavior

The defer function is passed to the onLoad callback. It returns a Promise that resolves once Bun has loaded all other modules. Await it when a module's contents depend on other modules. You can call the defer() function only once per onLoad callback.

onEnd callback signature and behavior

onEnd(callback: (result: BuildOutput) => void | Promise<void>): void. Registers a callback that runs after the bundle is complete. The callback receives the BuildOutput object containing build results including output files and build messages. The callback can return a Promise, and Bun.build() does not resolve until all onEnd() callbacks have completed.

Native plugins overview

Native plugins are NAPI modules that expose lifecycle hooks as C ABI functions. They can run on multiple threads, making them much faster than JavaScript plugins. They also skip work such as UTF-8 to UTF-16 conversion needed to pass strings to JavaScript.

onBeforeParse callback signature

onBeforeParse(args: { filter: RegExp; namespace?: string }, callback: { napiModule: NapiModule; symbol: string; external?: unknown }): void. The onBeforeParse() callback runs immediately before Bun's bundler parses a file. It receives the file's contents and can optionally return new source code. The callback can be called from any thread, so the NAPI module implementation must be thread-safe.

Creating native plugin in Rust with bun-native-plugin

To create a native plugin in Rust, install @napi-rs/cli globally and run napi new. Then install the bun-native-plugin crate. Use the bun_native_plugin::define_bun_plugin! macro to define the plugin and its name. Use the #[bun] macro on functions that implement native plugin hooks like onBeforeParse.

onResolve example: redirecting imports

This example shows how to redirect all imports of paths starting with 'images/' to './public/images/': build.onResolve({ filter: /.*/, namespace: 'file' }, args => { if (args.path.startsWith('images/')) { return { path: args.path.replace('images/', './public/images/') }; } });

onLoad example: env plugin

This example creates an env plugin that transforms imports of 'env' into a JavaScript module exporting environment variables. It uses onResolve to redirect 'env' imports to the 'env' namespace, then onLoad to return the environment variables as JSON: const envPlugin: BunPlugin = { name: 'env plugin', setup(build) { build.onResolve({ filter: /^env$/ }, () => ({ path: 'env', namespace: 'env' })); build.onLoad({ filter: /.*/, namespace: 'env' }, args => { return { contents: `export default ${JSON.stringify(process.env)}`, loader: 'js' }; }); } };

defer example: tracking unused exports

This example uses defer() to wait for all modules to be loaded before generating a stats file. The onLoad callback for .ts files tracks all imports. The onLoad callback for stats.json calls await defer() to ensure all other files have been processed, then returns JSON containing import statistics.

onEnd example: conditional S3 upload

This example shows how to use onEnd to conditionally upload build outputs to S3 only if the build succeeded: build.onEnd(async result => { if (!result.success) return; for (const output of result.outputs) { await uploadToS3(output); } });

onStart example with async operations

This example shows multiple onStart callbacks with async operations: one that sleeps for 10 seconds, and another that writes the current timestamp to a file using shell script. Bun waits for both to complete before continuing.

Native plugin onBeforeParse Rust implementation example

This Rust example implements onBeforeParse to replace all occurrences of 'foo' with 'bar'. It uses define_bun_plugin! to define the plugin name, and the #[bun] macro on the function. The function receives handle: &mut OnBeforeParse, calls handle.input_source_code() to get the source, performs the replacement, and calls handle.set_output_source_code() to set the modified code.

Using native plugin in Bun.build

To use a native plugin in Bun.build(), import the NAPI module and call build.onBeforeParse() with an object containing napiModule (the imported module), symbol (the function name as a string), and optional external (shared state from the module).

Plugins configuration for Bun.serve() in bunfig.toml

To configure plugins for Bun.serve(), add a plugins array in the [serve.static] section of bunfig.toml. The plugins array accepts any JS file or module that exports a valid bundler plugin object with a name and setup field. Plugins are lazily resolved and loaded, and are used to bundle routes.

TailwindCSS plugin for Bun.serve()

To use TailwindCSS with Bun.serve(), install tailwindcss and bun-plugin-tailwind with: bun add tailwindcss bun-plugin-tailwind. Then add [serve.static] plugins = ["bun-plugin-tailwind"] to bunfig.toml. Import tailwindcss in HTML with <link rel="stylesheet" href="tailwindcss" /> or in CSS with @import "tailwindcss";

Custom bundler plugins for Bun.serve()

Custom plugins can be defined as JS files that export a BunPlugin object with name and setup(build) fields. Example: const myPlugin: BunPlugin = { name: "my-custom-plugin", setup(build) { build.onLoad({ filter: /\.custom$/ }, async args => { const text = await Bun.file(args.path).text(); return { contents: `export default ${JSON.stringify(text)};`, loader: "js" }; }); } }. Reference the plugin file in [serve.static] plugins in bunfig.toml.

Give your agent this brain