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 · Runtime · all subjects

bun apis/bundler

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

Bun.build bundler API

Bun.build is the bundler API provided by Bun for bundling JavaScript/TypeScript code.

Bun.color macro example

Code: import {color} from 'bun' with {type: 'macro'}; console.log(color('#f00', 'css')); When built with 'bun build ./client-side.ts', the output is: console.log('red');

Bun.color bundle-time macro usage

Bun.color can be invoked at bundle time using a macro. Import with: import {color} from 'bun' with {type: 'macro'}. Then use normally in code. When running 'bun build', the color function calls are evaluated at compile time and replaced with their return values in the built output.

JSON5 bundler integration

When bundling with Bun, imported JSON5 files are parsed at build time and included as JavaScript modules. This means zero runtime JSON5 parsing overhead in production, smaller bundle sizes, and tree shaking of unused properties (for named imports).

PluginBuilder type definition

The PluginBuilder type has four properties: 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; namespace: string; defer: () => Promise<void> }) => { loader?: Loader; contents?: string; exports?: Record<string, any> }): void; config: BuildConfig.

Namespace concept in plugins

Every module has a namespace which prefixes the import in transpiled code. The default namespace is 'file'. A loader with filter: /\.yaml$/ and namespace: 'yaml:' transforms an import from './myfile.yaml' into 'yaml:./myfile.yaml'. Common namespaces are 'bun' for Bun-specific modules like 'bun:test' and 'bun:sqlite', and 'node' for Node.js modules like 'node:fs' and 'node:path'.

onStart hook signature and behavior

The onStart hook signature is onStart(callback: () => void): Promise<void> | void. It registers a callback that runs when the bundler starts a new bundle. The callback can return a Promise. After bundle process initialization, the bundler waits until all onStart() callbacks have completed before continuing.

onStart hook cannot modify build.config

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

onResolve hook signature and purpose

The onResolve hook signature is onResolve(args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string } | void): void. It customizes how a module is resolved. The first argument contains a filter RegExp and optional namespace that determine which modules the custom resolution applies to. The callback receives the path to the matching module and can return a new path for it. Bun reads the contents of the new path and parses it as a module.

onLoad hook signature and purpose

The onLoad hook signature is 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. After the bundler resolves a module, it needs to read and parse the module's contents. onLoad() modifies the contents of a module before Bun reads and parses it. The callback receives the matching module's path, namespace, default loader for that file, and a defer function. The callback can return a new contents string and new loader.

Native plugins overview

Native plugins are written as NAPI modules and can run on multiple threads, making them much faster than JavaScript plugins. They can also skip unnecessary work such as UTF-8 to UTF-16 conversion needed to pass strings to JavaScript. The onBeforeParse() lifecycle hook is available to native plugins.

onBeforeParse hook signature and purpose

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

Creating native plugins in Rust with bun-native-plugin

To create a native plugin in Rust, first run 'bun add -g @napi-rs/cli' and 'napi new'. Then install the crate with 'cargo add bun-native-plugin'. In lib.rs, use the 'bun_native_plugin::bun' proc macro to define a function implementing the native plugin. Use the 'define_bun_plugin!()' macro to define the plugin and its name. The #[bun] macro generates boilerplate code where the function argument type (e.g., &mut OnBeforeParse) tells the macro which hook is being implemented.

onResolve example - redirecting image imports

This example shows redirecting all imports to '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 - environment variable plugin

This example transforms all imports of the form 'import env from "env"' into a JavaScript module that exports environment variables: build.onLoad({ filter: /env/, namespace: 'file' }, args => { return { contents: `export default ${JSON.stringify(process.env)}`, loader: 'js', }; });

onStart example - asynchronous operations

This example shows two onStart callbacks: one that sleeps for 10 seconds and another that logs the bundle time to a file. Bun waits for both callbacks to complete before continuing the bundle process: await Bun.build({ entrypoints: ['./app.ts'], outdir: './dist', sourcemap: 'external', plugins: [ { name: 'Sleep for 10 seconds', setup(build) { build.onStart(async () => { await Bun.sleep(10_000); }); } }, { name: 'Log bundle time to a file', setup(build) { build.onStart(async () => { const now = Date.now(); await Bun.$`echo ${now} > bundle-time.txt`; }); } } ] });

defer() example - tracking unused exports

This example uses defer() to wait for all files to be loaded before emitting statistics. The first onLoad tracks imports from TypeScript files, and the second onLoad for stats.json awaits defer() to ensure all files have been processed: build.onLoad({ filter: /\.ts/ }, async ({ path }) => { const contents = await Bun.file(path).arrayBuffer(); const imports = transpiler.scanImports(contents); for (const i of imports) { trackedImports[i.path] = (trackedImports[i.path] || 0) + 1; } return undefined; }); build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => { await defer(); return { contents: `export default ${JSON.stringify(trackedImports)}`, loader: 'json', }; });

onBeforeParse Rust native plugin example

This example implements onBeforeParse in Rust to replace all occurrences of 'foo' with 'bar': use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader}; use napi_derive::napi; define_bun_plugin!("replace-foo-with-bar"); #[bun] pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> { let input_source_code = handle.input_source_code()?; let loader = handle.output_loader(); let output_source_code = input_source_code.replace("foo", "bar"); handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX); Ok(()) }

Using native plugin in Bun.build

To use a native plugin in Bun.build(): import myNativeAddon from './my-native-addon'; Bun.build({ entrypoints: ['./app.tsx'], plugins: [ { name: 'my-plugin', setup(build) { build.onBeforeParse( { namespace: 'file', filter: /\.tsx$/ }, { napiModule: myNativeAddon, symbol: 'replace_foo_with_bar' } ); } } ] });

defer() function in onLoad callback

The onLoad callback receives a defer function which returns a Promise that resolves once all other modules have been loaded. Await it when a module's contents depend on other modules. The defer() function can only be called once per onLoad callback.

Plugin structure and registration

A Bun plugin is a JavaScript object with a 'name' property and a 'setup' function. The plugin receives a build object as an argument to setup(). Plugins are passed to Bun.build() in the 'plugins' array.

Plugin lifecycle hooks overview

Bun plugins support four lifecycle hooks: onStart() runs once when the bundler starts a bundle; onResolve() runs before a module is resolved; onLoad() runs before a module is loaded; onBeforeParse() runs zero-copy native addons in the parser thread before a file is parsed.

Loader types available in plugins

The Loader type in plugins can be one of: 'js', 'jsx', 'ts', 'tsx', 'json', 'jsonc', 'toml', 'yaml', 'file', 'napi', 'wasm', 'text', 'css', or 'html'.

TOML bundler integration

When bundling with Bun using bun build, the bundler parses imported TOML at build time and includes it as a JavaScript module in the output. This results in zero runtime TOML parsing overhead in production, smaller bundle sizes, and enables tree shaking of unused properties when using named imports. Dynamic imports of TOML files are also supported with await import('./config.toml').

Bun bundler parses YAML at build time

When bundling an application that imports YAML files with 'bun build', Bun parses the YAML at build time and includes it as a JavaScript module. This eliminates runtime YAML parsing overhead, reduces bundle sizes, and enables tree shaking of unused configuration via named imports.

Give your agent this brain