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

bundler & build

75 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Plugin lifecycle hooks overview

Bun's plugin system includes five 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; onEnd() runs after the bundle is complete.

Plugin structure and setup

A plugin is a JavaScript object with a name property and a setup function. The setup function receives a PluginBuilder object with methods to register lifecycle callbacks. Plugins are passed to Bun.build() in the plugins array.

Loader types in plugins

The Loader type includes: 'js', 'jsx', 'ts', 'tsx', 'json', 'jsonc', 'toml', 'yaml', 'file', 'napi', 'wasm', 'text', 'css', 'html'.

Namespaces in plugins

Every module has a namespace that prefixes the import in transpiled code. The default namespace is 'file'. Common namespaces include 'bun' for Bun-specific modules like 'bun:test' and 'bun:sqlite', and 'node' for Node.js modules like 'node:fs' and 'node:path'. Custom namespaces can be defined with the namespace property in onLoad and onResolve.

onStart hook signature and behavior

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

onStart hook cannot modify build.config

onStart() callbacks cannot modify the build.config object. To mutate build.config, do so directly in the setup() function.

onResolve hook signature and purpose

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

onLoad hook signature and purpose

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 onLoad callback modifies the contents of a module before Bun reads and parses it. The callback receives the path, namespace, default loader, and a defer function. It can return a new contents string, a new loader, or exports.

defer() function in onLoad

The defer() function passed to onLoad returns a Promise that resolves once all other modules have been loaded. It should be awaited when a module's contents depend on other modules. The defer() function can only be called once per onLoad callback.

onBeforeParse native plugin hook

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. This hook can be called from any thread, so the NAPI module implementation must be thread-safe.

onEnd hook signature and behavior

onEnd(callback: (result: BuildOutput) => void | Promise<void>): void. The onEnd hook registers a callback to run 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. The promise returned by Bun.build() does not resolve until all onEnd() callbacks have completed.

onEnd hook access to build results

The onEnd callback receives a BuildOutput object with properties including outputs (array of output files) and logs (array of build messages). The result object includes a success property that indicates whether the build completed successfully.

Native plugins for high performance

Bun's bundler uses multiple threads to load and parse modules in parallel. JavaScript plugins run on a single thread. Native plugins are NAPI modules that run on multiple threads, making them much faster than JavaScript plugins and avoiding UTF-8 to UTF-16 conversion overhead.

Creating native plugins in Rust

To create a native plugin in Rust, use '@napi-rs/cli' with 'bun add -g @napi-rs/cli' and 'napi new'. Install the 'bun-native-plugin' crate with 'cargo add bun-native-plugin'. Use the bun_native_plugin::bun proc macro in lib.rs to define functions implementing native plugin hooks.

onBeforeParse Rust implementation example

Use define_bun_plugin!() to define the plugin and its name. Implement onBeforeParse with #[bun] macro on a function that takes &mut OnBeforeParse. Call handle.input_source_code() to fetch input, handle.output_loader() to get the loader, and handle.set_output_source_code() to set output with a BunLoader value.

onEnd hook example with S3 upload

The onEnd hook can be used to upload build outputs after completion. Check result.success before processing. Iterate through result.outputs and perform async operations like uploadToS3(output) for each output file.

Bytecode caching basic usage with --bytecode flag

Enable bytecode caching with the --bytecode flag in bun build. Without --format, the output defaults to CommonJS. The build writes two files: the bundled JavaScript (.js) and the bytecode cache file (.jsc). At runtime, Bun automatically detects and uses the .jsc file. Example: bun build ./index.ts --target=bun --bytecode --outdir=./dist produces dist/index.js and dist/index.js.jsc.

Bytecode with standalone executables --compile flag

When creating an executable with --compile, Bun embeds the bytecode in the binary. ESM bytecode requires --compile because Bun embeds module metadata (import/export information) in the compiled binary. CommonJS bytecode works with or without --compile. The resulting executable contains both the code and the bytecode.

ESM bytecode requires --compile

ESM bytecode requires --compile because Bun embeds module metadata (import/export information) in the compiled binary. With this metadata, the JavaScript engine skips parsing entirely at runtime. Without --compile, ESM bytecode would still require parsing the source to analyze module dependencies, which defeats the purpose of bytecode caching.

Bytecode combining with minification and source maps

Bytecode can be combined with --minify and --sourcemap optimizations. The --minify flag reduces code size before generating bytecode (less code results in less bytecode). The --sourcemap flag preserves error reporting so errors still point to original source. The --bytecode flag eliminates parsing overhead. Example: bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli

Bytecode performance improvement by application size

Performance improvement scales with codebase size: Small CLI (< 100 KB) provides 1.5-2x faster startup. Medium-large app (> 5 MB) provides 2.5x-4x faster startup. Larger applications benefit more because they have more code to parse.

Bytecode not portable across Bun versions

Bytecode is not portable across Bun versions. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions. When you update Bun, you must regenerate bytecode. If bytecode doesn't match the current Bun version, Bun ignores it and falls back to parsing the JavaScript source. Best practice: Generate bytecode as part of CI/CD build process. Do not commit .jsc files to git. Regenerate them whenever you update Bun.

Bytecode requires both .js and .jsc files at runtime

Bytecode doesn't replace your JavaScript. You must deploy both files: the .js file (bundled source code) and the .jsc file (bytecode cache). At runtime, Bun loads the .js file and checks the .jsc file. Bun validates the bytecode hash matches the source. If valid, Bun uses the bytecode. If invalid, Bun falls back to parsing the source.

Bytecode does not obscure source code

Bytecode does not obscure your source code. It is an optimization, not a security measure.

Verify bytecode usage with .jsc file check

To verify bytecode is being used, check that the .jsc file exists. The .jsc file should be 2-8x larger than the .js file. To log whether the bytecode is used, set BUN_JSC_verboseDiskCache=1 in the environment. On a cache hit, Bun logs '[Disk Cache] Cache hit for sourceCode'. On a cache miss, Bun logs '[Disk Cache] Cache miss for sourceCode'.

Bytecode common issue: silently ignored

Bytecode silently ignored is usually caused by a Bun version update. The cache version doesn't match, so bytecode is rejected. Regenerate bytecode to fix this issue.

Bytecode file size typically 2-8x larger than source

Bytecode files are typically 2-8x larger than the source code. This is because bytecode instructions are verbose, constant pools store everything, per-function metadata is included for each function, profiling data structures are allocated, and control flow is pre-computed. Bytecode compresses well with gzip/brotli (60-70% compression).

Bytecode architecture-independent but version-specific

Bytecode is architecture-independent. You can build on macOS ARM64 and deploy to Linux x64, or build on Linux x64 and deploy to AWS Lambda ARM64. However, bytecode is not stable across Bun versions. The cache version in the .jsc file header is a hash of the JavaScriptCore framework. When versions don't match, the bytecode is silently rejected and Bun falls back to parsing the .js source code.

.jsc file format structure

A .jsc file contains: Header section with cache version (hash tied to JavaScriptCore framework version) and code block type tag. SourceCodeKey with source code hash, source code length, and compilation flags. Bytecode instructions with instruction stream, metadata table, jump targets, and switch tables. Constants and identifiers with constant pool, identifier table, and source code representation markers. Function metadata with register allocation, code features bitmask, lexically scoped features, and parse mode. Nested structures with function declarations/expressions, exception handlers, and expression info.

Bytecode does not embed source code

Bytecode does not embed your source code. The JavaScript source is stored separately in the .js file. The bytecode only stores a hash and length of the source. At load time, Bun validates the bytecode matches the current source code. This is why both .js and .jsc files must be deployed: the .jsc file is useless without its corresponding .js file.

Unlinked bytecode vs linked bytecode

Unlinked bytecode is what's cached in .jsc files. It contains compiled bytecode instructions, structural information, constants, identifiers, and control flow information, but does not contain pointers to runtime objects, JIT-compiled machine code, profiling data, or call link information. Unlinked bytecode is immutable and shareable. Linked bytecode is created at runtime and adds call link information, profiling data, JIT compilation state, and runtime object pointers. This separation allows caching of expensive work while still collecting runtime profiling data and applying JIT optimizations.

Bun bundler bundles by default

Unlike esbuild, Bun's bundler bundles by default. No --bundle flag is needed. To transpile each file individually without bundling, use Bun.Transpiler.

Bun bundler has no built-in development server

Unlike esbuild, Bun's bundler has no built-in development server. Use it with Bun.serve and other runtime APIs to get the same effect. esbuild's HTTP options don't apply.

Bun bundler performance vs esbuild

Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark, which involves bundling 10 copies of three.js from scratch with sourcemaps and minification.

CLI flag syntax differences between esbuild and bun build

In Bun's CLI, boolean flags like --minify take no argument. Flags that take one argument, like --outdir, can be written as --outdir out or --outdir=out. Some flags like --define can be repeated: --define foo=bar --define bar=baz.

bun build --define syntax differs from esbuild

esbuild uses --define:K=V syntax with a colon. Bun uses --define K=V without a colon. Example: esbuild --define:foo=bar becomes bun build --define foo=bar.

bun build --external syntax differs from esbuild

esbuild uses --external:pkg syntax with a colon. Bun uses --external pkg without a colon. Example: esbuild --external:react becomes bun build --external react.

bun build --format supported values

Bun's bun build --format supports esm, cjs, and iife. esbuild defaults to iife.

bun build --loader syntax and supported loaders differ from esbuild

Bun's --loader syntax is --loader .ext:loader (example: bun build app.ts --loader .svg:text), different from esbuild's --loader:.ext=loader syntax. Bun supports a different set of built-in loaders than esbuild. The esbuild loaders dataurl, binary, base64, copy, and empty are not implemented in Bun.

bun build --target replaces esbuild --platform

Bun renamed esbuild's --platform flag to --target for consistency with tsconfig. Bun's --target does not support the neutral value that esbuild supports.

bun build does not support syntactic down-leveling

Bun's bundler performs no syntactic down-leveling. The esbuild --target flag for transpiling syntax to older JavaScript versions is not supported in Bun.

bun build --no-bundle disables bundling

Bun always bundles by default. To disable bundling, use the --no-bundle flag.

bun build --asset-naming replaces esbuild --asset-names

Bun uses --asset-naming instead of esbuild's --asset-names, renamed for consistency with naming in the JS API.

bun build --chunk-naming replaces esbuild --chunk-names

Bun uses --chunk-naming instead of esbuild's --chunk-names, renamed for consistency with naming in the JS API.

bun build --entry-naming replaces esbuild --entry-names

Bun uses --entry-naming instead of esbuild's --entry-names, renamed for consistency with naming in the JS API.

bun build --feature flag enables compile-time dead-code elimination

The --feature flag is Bun-specific and enables feature flags for compile-time dead-code elimination through import { feature } from "bun:bundle".

bun build --ignore-dce-annotations replaces esbuild --ignore-annotations

Bun uses --ignore-dce-annotations instead of esbuild's --ignore-annotations.

bun build --jsx-runtime options

Bun's --jsx-runtime flag supports "automatic" (uses jsx transform) and "classic" (uses React.createElement).

Bun jsx configuration from tsconfig.json

Bun reads compilerOptions.jsx from tsconfig.json to determine a default. If compilerOptions.jsx is "react-jsx", or if NODE_ENV=production, Bun uses the jsx transform. Otherwise, it uses jsxDEV. The bundler does not support the preserve option.

bun build --root replaces esbuild --outbase

Bun uses --root instead of esbuild's --outbase flag.

bun build --tsconfig-override replaces esbuild --tsconfig

Bun uses --tsconfig-override instead of esbuild's --tsconfig flag.

bun build does not allow overwriting

Bun does not support the --allow-overwrite flag. Overwriting is never allowed in Bun.

bun build --banner and --footer only apply to js bundles

The --banner and --footer flags in bun build only apply to JavaScript bundles, not other asset types.

bun build always enables color output

The --color flag is always enabled in bun build. It cannot be disabled.

bun build tree-shaking is always enabled

Tree-shaking in bun build is always true. It cannot be disabled.

bun build --drop flag

Bun supports the --drop flag for dead-code elimination, similar to esbuild.

Bun.build() entrypoints capitalization

In Bun.build() JavaScript API, the parameter is entrypoints (lowercase 'p'), whereas esbuild uses entryPoints (camelCase).

Bun.build() naming parameter

Bun.build() supports a naming key that can either be a string or an object with granular naming options. When a string, it is equivalent to entryNames. As an object, it can have entry, asset, and chunk properties. Uses the same templating syntax as esbuild, but [ext] must be included explicitly. Example: naming: { entry: "[name].[ext]", asset: "[name].[ext]", chunk: "[name].[ext]" }

Bun.build() target platform values

Bun.build() supports target values of "bun", "node", and "browser" (the default). It does not support "neutral".

Bun.build() jsx parameter structure

In Bun.build(), JSX configuration uses a jsx object with properties: runtime (supports "automatic" and "classic"), development, factory, fragment, importSource, and sideEffects.

Give your agent this brain