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

bundler/behavior

50 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 bundler always bundles by default

Unlike esbuild, Bun bundles by default; no `--bundle` flag is needed. To transpile each file individually, use `Bun.Transpiler` instead.

Bun bundler has no built-in development server

Unlike esbuild, Bun's bundler has no built-in development server. To get the same effect, use it with `Bun.serve` and other runtime APIs. esbuild's HTTP options do not apply in Bun.

HMR enabled by default in full-stack dev server

Hot Module Replacement (HMR) is enabled by default when using Bun's full-stack development server. HMR updates modules in a running application without a full page reload, preserving application state.

import.meta.hot API check and tree-shaking

You can check for HMR API availability with `if (import.meta.hot)`, which tree-shakes in production. Bun dead-code-eliminates calls to all HMR APIs in production builds.

import.meta.hot must be called directly without indirection

The import.meta.hot APIs must be called without indirection for tree-shaking to work in production. Invalid patterns include assigning `hot` to a variable, assigning `import.meta` to a variable, or passing to a function. Valid usage requires calling the full phrase 'import.meta.hot.<API>' directly, except that `data` can be passed to functions.

Disable HMR with hmr option

To disable HMR in Bun.serve, set the development option to `{ hmr: false }`.

import.meta.hot API methods and status

The import.meta.hot API includes the following methods: - hot.accept(): ✅ Indicate that a hot update can be replaced gracefully. - hot.data: ✅ Persist data between module evaluations. - hot.dispose(): ✅ Add a callback function to run when a module is about to be replaced. - hot.invalidate(): ❌ Not implemented. - hot.on(): ✅ Attach an event listener. - hot.off(): ✅ Remove an event listener from `on`. - hot.send(): ❌ Not implemented. - hot.prune(): 🚧 Callback is currently never called. - hot.decline(): ✅ No-op to match Vite's `import.meta.hot`.

import.meta.hot.accept() without arguments

The accept() method without arguments indicates that a module can be hot-replaced by re-evaluating the file. After a hot update, Bun automatically patches the module's importers. This creates a hot-reloading boundary for all files that the module imports. When a dependency is updated, the update bubbles up to this module which re-evaluates, and files that import this module are patched to import the new version. If only this module is updated, only that file is re-evaluated and dependencies are reused.

import.meta.hot.accept() with callback

When passed a callback, import.meta.hot.accept calls the callback with the new module instead of patching importers. The newModule parameter is undefined when a SyntaxError occurs. This form works as it does in Vite.

import.meta.hot.accept() for single dependency

import.meta.hot.accept can accept a dependency path as a string. When that dependency is updated, Bun calls the callback with the new module. Example: `import.meta.hot.accept('./foo', (newModule) => { ... })`

import.meta.hot.accept() with multiple dependencies

import.meta.hot.accept can accept an array of dependency paths. The callback receives an array where each item corresponds to the updated module or undefined if that module had a syntax error.

import.meta.hot.data persists state between evaluations

import.meta.hot.data carries state from the previous version of a module to the new one across a hot replacement. Writing to import.meta.hot.data also marks the module as self-accepting (equivalent to calling import.meta.hot.accept()). In production, data is inlined to be {}, meaning it cannot be used as a state holder.

import.meta.hot.dispose() callback behavior

import.meta.hot.dispose() attaches an on-dispose callback that is called just before the module is replaced with another copy and after the module is detached (when all imports are removed). If the callback returns a promise, module replacement is delayed until the module is disposed. All dispose callbacks are called in parallel. This callback is not called on route navigation or when the browser tab closes.

import.meta.hot.prune() callback behavior

import.meta.hot.prune() attaches an on-prune callback that is called when all imports to this module are removed but the module was previously loaded. Use it to clean up resources that were created when the module was loaded. Unlike dispose(), it pairs better with accept() and data for managing stateful resources. Currently the prune callback is never called.

import.meta.hot.on() and off() for event listeners

Use import.meta.hot.on() and off() to listen for events from the HMR runtime. Event names carry a prefix so that plugins do not conflict with each other. When a file is replaced, all of its event listeners are automatically removed.

Built-in HMR events

The HMR runtime emits the following built-in events: - bun:beforeUpdate: before a hot update is applied. - bun:afterUpdate: after a hot update is applied. - bun:beforeFullReload: before a full page reload happens. - bun:beforePrune: before prune callbacks are called. - bun:invalidate: when a module is invalidated with import.meta.hot.invalidate(). - bun:error: when a build or runtime error occurs. - bun:ws:disconnect: when the HMR WebSocket connection is lost, indicating the development server is offline. - bun:ws:connect: when the HMR WebSocket connects or re-connects. For Vite compatibility, these events are also available with the `vite:*` prefix instead of `bun:*`.

Full page reload when no modules accept HMR

When no modules call import.meta.hot.accept() (and there isn't React Fast Refresh or a plugin calling it), the page reloads when the file updates and a console warning shows which files were invalidated.

Example: import.meta.hot.accept() without arguments

```ts // index.ts import { getCount } from "./foo.ts"; console.log("count is ", getCount()); import.meta.hot.accept(); export function getNegativeCount() { return -getCount(); } ``` This example shows how to call accept() without arguments to make a module hot-replaceable. The module creates a hot-reloading boundary for all files it imports. When dependencies update, the update bubbles up and files importing this module are patched to the new version.

Example: import.meta.hot.accept() with callback

```ts export const count = 0; import.meta.hot.accept(newModule => { if (newModule) { // newModule is undefined when SyntaxError happened console.log("updated: count is now ", newModule.count); } }); ``` This example shows how to use accept() with a callback. When the module is updated, the callback is called with the new module (or undefined if a SyntaxError occurred).

Example: import.meta.hot.accept() for a specific dependency

```ts import { count } from "./foo"; import.meta.hot.accept("./foo", (newModule) => { if (!newModule) return; console.log("updated: count is now ", count); }); ``` This example shows how to accept updates for a specific dependency by passing the dependency path as a string. When that dependency is updated, the callback receives the new module.

Example: import.meta.hot.accept() with multiple dependencies

```ts import.meta.hot.accept(["./foo", "./bar"], newModules => { // newModules is an array where each item corresponds to the updated module // or undefined if that module had a syntax error }); ``` This example shows how to accept updates for multiple dependencies by passing an array of paths. The callback receives an array of updated modules, with undefined for any that had errors.

Example: Using import.meta.hot.data with React

```tsx import { createRoot } from "react-dom/client"; import { App } from "./app"; const root = (import.meta.hot.data.root ??= createRoot(elem)); root.render(<App />); // re-use an existing root ``` This example shows how to use import.meta.hot.data to persist a React root across hot updates, preserving component state.

Example: Using import.meta.hot.dispose() for cleanup

```ts const sideEffect = setupSideEffect(); import.meta.hot.dispose(() => { sideEffect.cleanup(); }); ``` This example shows how to use dispose() to clean up side effects when a module is replaced.

Example: Using import.meta.hot.prune() with WebSocket

```ts import { something } from "./something"; // Initialize or re-use a WebSocket connection export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin)); // If the module's import is removed, clean up the WebSocket connection. import.meta.hot.prune(() => { ws.close(); }); ``` This example shows how to use prune() to clean up a WebSocket connection when the module's imports are removed, managing stateful resources better than dispose().

Example: Listening to HMR events

```ts import.meta.hot.on("bun:beforeUpdate", () => { console.log("before a hot update"); }); ``` This example shows how to attach an event listener to HMR events using import.meta.hot.on().

Single Page App (SPA) behavior

When you pass a single .html file to Bun, Bun uses it as a fallback route for all paths. This means routes like /about and /users/123 serve the same HTML file, allowing your client-side router to handle the navigation. This works with no configuration for React or other SPAs.

Path normalization for multiple HTML files

When multiple HTML files are provided as entry points, Bun chooses the base path from the longest common prefix among all the files. For example, with ./index.html, ./about/index.html, and ./about/foo/index.html, the routes become /, /about, and /about/foo respectively.

Edit files in browser with Automatic Workspace Folders

Bun's frontend dev server supports Automatic Workspace Folders in Chrome DevTools, allowing you to save edits to files from the browser.

What assets Bun processes in HTML

Bun automatically handles: scripts (<script src>) are run through the JavaScript/TypeScript/JSX bundler; stylesheets (<link rel="stylesheet">) are run through the CSS parser & bundler; images (<img>, <picture>) are copied and hashed; media (<video>, <audio>, <source>) are copied and hashed; any <link> tag with an href attribute pointing to a local file is rewritten to the new path and hashed. All paths are resolved relative to the HTML file.

HTML imports in JavaScript

HTML bundling in Bun is a wrapper around Bun's support for HTML imports in JavaScript, providing integration between HTML and JavaScript modules.

Adding backend to frontend with routes option

To add a backend to your frontend, use the `routes` option in `Bun.serve`. See the full-stack documentation for more details.

Dev server features with zero configuration

With no configuration, Bun's development server provides: automatic bundling of HTML, JavaScript, and CSS; multi-entry support for multiple HTML entry points and glob entry points; TypeScript and JSX support by default; smart configuration reading tsconfig.json for paths, JSX options, and experimental decorators; plugin support including TailwindCSS; ESM and CommonJS support; CSS bundling and minification; and asset management with copying, hashing, and path rewriting.

JavaScript content types: .js .jsx .ts .tsx etc

JavaScript files (.js, .jsx, .cjs, .mjs, .mts, .cts, .ts, .tsx) are transpiled using Bun's built-in transpiler. TypeScript/JSX syntax is converted to vanilla JavaScript with dead code elimination and tree shaking applied. Recent ECMAScript syntax is preserved as-is in output.

JSON and data file loaders

JSON (.json), JSONC (.jsonc), TOML (.toml), and YAML (.yaml, .yml) files are parsed and inlined as JavaScript objects. TXT (.txt) files are read and inlined as strings. Example: import pkg from './package.json'.

HTML file bundling

HTML files (.html) are processed during bundling, and any referenced assets (scripts, stylesheets, images) are bundled.

Binary file handling: .node and .wasm

Node native modules (.node) and WebAssembly (.wasm) files are supported by the Bun runtime, but during bundling they are treated as assets and copied as-is.

Asset file handling

Files with unrecognized extensions are treated as assets. They are copied as-is into outdir and the import is resolved as a path to the file. The import statement becomes a string variable containing the asset path.

Standalone HTML bundle output

Bun can bundle an entire frontend into a single .html file with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, and WASM are all inlined into one file.

What gets inlined in standalone HTML

Bun inlines every local asset referenced by relative paths: <script src="./app.tsx"> becomes <script type="module">...bundled code...</script>; <link rel="stylesheet" href="./styles.css"> becomes <style>...bundled CSS...</style>; <img src="./logo.png"> becomes <img src="data:image/png;base64,...">; <img src="./icon.svg"> becomes <img src="data:image/svg+xml;base64,...">; <video src="./demo.mp4"> becomes <video src="data:video/mp4;base64,...">; <audio src="./click.wav"> becomes <audio src="data:audio/x-wav;base64,...">; <source src="./clip.webm"> becomes <source src="data:video/webm;base64,...">; <video poster="./thumb.jpg"> becomes <video poster="data:image/jpeg;base64,...">; <link rel="icon" href="./favicon.ico"> becomes <link rel="icon" href="data:image/x-icon;base64,...">; <link rel="manifest" href="./app.webmanifest"> becomes <link rel="manifest" href="data:application/manifest+json;base64,...">; CSS url("./bg.png") becomes CSS url(data:image/png;base64,...); CSS @import "./reset.css" is flattened into the <style> tag; CSS url("./font.woff2") becomes CSS url(data:font/woff2;base64,...); JS import "./styles.css" is merged into the <style> tag. External URLs like CDN links or absolute URLs are left untouched.

Standalone HTML how it works

When using --compile --target=browser with an HTML entrypoint, Bun: 1) Parses the HTML and discovers all <script>, <link>, <img>, <video>, <audio>, <source>, and other asset references; 2) Bundles all JavaScript/TypeScript/JSX into a single module; 3) Bundles all CSS including @import chains and CSS imported from JS into a single stylesheet; 4) Converts every relative asset reference into a base64 data: URI; 5) Inlines the bundled JS as <script type="module"> before </body>; 6) Inlines the bundled CSS as <style> in <head>; 7) Outputs a single .html file with no external dependencies.

Standalone HTML multiple files

You can pass multiple HTML files as entrypoints to bun build --compile --target=browser. Each produces its own standalone HTML file.

Tailwind CSS with standalone HTML

Use the bun-plugin-tailwind plugin with standalone HTML. Install with `bun install --dev bun-plugin-tailwind`. Reference Tailwind in HTML with `<link rel="stylesheet" href="tailwindcss" />` or in CSS. Build with the JavaScript API passing plugins option: `await Bun.build({ entrypoints: ["./index.html"], compile: true, target: "browser", outdir: "./dist", plugins: [require("bun-plugin-tailwind")] })`. The generated Tailwind CSS is inlined directly into the HTML file as a <style> tag.

HTML routes processing with HTMLRewriter

When an HTML file is imported and used as a route, Bun uses HTMLRewriter to scan for `<script>` and `<link>` tags, runs Bun's JavaScript & CSS bundler on them, transpiles TypeScript, JSX, and TSX, downlevels CSS with Bun's CSS parser and serves the result.

Development vs Production mode comparison

Development mode: source maps enabled, minification disabled, hot reloading enabled, asset bundling on each request, browser console → terminal, detailed error details. Production mode: source maps disabled, minification enabled, hot reloading disabled, asset bundling cached, console logging disabled, minimal error details.

Runtime bundling in production with development: false

Setting `development: false` in `Bun.serve()` enables in-memory caching of bundled assets. Bun bundles assets lazily on the first request to an .html file and caches the result in memory until the server restarts. This also enables Cache-Control and ETag headers and minifies JavaScript/TypeScript/TSX/JSX files.

Inline environment variables configuration

Configure the `env` option in `[serve.static]` section of `bunfig.toml`. Options: `env = "PUBLIC_*"` (only inline vars starting with PUBLIC_, recommended), `env = "inline"` (inline all environment variables), `env = "disable"` (disable replacement, default). Only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access.

Sourcemap configuration in bunfig.toml

Set the `sourcemap` option in `[serve.static]` section: `sourcemap = "linked"` (serve sourcemaps in production too), `sourcemap = "inline"` (embed sourcemaps in chunks), `sourcemap = "external"` (emit .map files without sourceMappingURL comment), `sourcemap = false` (never generate sourcemaps). In development, Bun generates linked sourcemaps by default. In production, sourcemaps are disabled by default.

HTML processing pipeline steps

Bun's HTML processing: (1) <script> Processing - transpiles TypeScript, JSX, TSX; bundles dependencies; generates sourcemaps; minifies when development is not true. (2) <link> Processing - processes CSS imports; concatenates CSS files; rewrites URLs and asset paths with content-addressable hashes. (3) <img> & Asset Processing - rewrites links with hashes; inlines small CSS assets to data: URLs. (4) HTML Rewriting - combines all <script> tags into single tag with hash; combines all <link> tags into single tag with hash; outputs new HTML. (5) Serving - exposes output files as static routes.

HTML processing output example

Input HTML with multiple <script> and <link> tags becomes a single <link> to a bundled CSS file with hash (e.g., `/index-[hash].css`) and a single <script type="module"> pointing to bundled JS with hash (e.g., `/index-[hash].js`). Asset and module paths are rewritten with content-addressable hashes.

Planned features for Bun fullstack dev server

Planned future features include: file-based routing for API endpoints, built-in SSR support, and enhanced plugin ecosystem.

Give your agent this brain