Skip to content
2 changes: 1 addition & 1 deletion docs/bundler/esbuild.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ In Bun's CLI, boolean flags like `--minify` take no argument. Flags that take on
| `--packages` | `--packages` | No differences |
| `--platform` | `--target` | Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. |
| `--serve` | n/a | Not applicable |
| `--sourcemap` | `--sourcemap` | No differences |
| `--sourcemap` | `--sourcemap` | Supports `linked` (the default when no value is given), `external`, `inline`, and `none`. Does not support esbuild's `both`. |
| `--splitting` | `--splitting` | No differences |
| `--target` | n/a | Not supported. Bun's bundler performs no syntactic down-leveling. |
| `--watch` | `--watch` | No differences |
Expand Down
10 changes: 6 additions & 4 deletions docs/bundler/executables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -757,10 +757,12 @@ import styles from "./styles.css" with { type: "file" };
import { file, serve } from "bun";

serve({
// Inside the compiled executable, file() on an embedded path returns an
// in-memory Blob, which routes only accepts wrapped in a Response.
routes: {
"/favicon.ico": file(favicon),
"/logo.png": file(logo),
"/styles.css": file(styles),
"/favicon.ico": new Response(file(favicon)),
"/logo.png": new Response(file(logo)),
"/styles.css": new Response(file(styles)),
},
fetch(req) {
return new Response("Not found", { status: 404 });
Expand Down Expand Up @@ -875,7 +877,7 @@ const html = await Bun.file(path.join(publicDir, "index.html")).text();

Pass `--asset` multiple times to embed several directories (for example `--asset ./client --asset ./prerendered` for a SvelteKit build). Bun embeds only regular files; it skips symlinks and empty subdirectories inside the tree.

You can also embed individual files via the `with { type: "file" }` import attribute or by adding them as extra entry points. Bun renames imported assets according to `--asset-naming` (default `[name]-[hash].[ext]`):
You can also embed individual files via the `with { type: "file" }` import attribute or, for files that use the `file` loader (images, fonts, and so on) as well as `.wasm` and `.node` files, by adding them as extra entry points. Bun renames imported assets according to `--asset-naming` (default `[name]-[hash].[ext]`):

```ts
import icon from "./public/assets/icon.png" with { type: "file" };
Expand Down
2 changes: 1 addition & 1 deletion docs/bundler/fullstack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const server = serve({
},
async POST(req) {
const { name, email } = await req.json();
const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email}) RETURNING *`;
return Response.json(user);
},
},
Expand Down
6 changes: 3 additions & 3 deletions docs/bundler/hot-reloading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ Indicates that a dependency's module can be accepted. When the dependency is upd

```ts title="index.ts" icon="/icons/typescript.svg"
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
// newModules holds the updated module at its index and
// undefined for the other dependencies
});
```

This variant accepts an array of dependencies. The callback receives the updated modules, and `undefined` for any that had errors.
This variant accepts an array of dependencies. The callback receives an array with the updated module at its index and `undefined` for the other dependencies.

## import.meta.hot.data

Expand Down
10 changes: 6 additions & 4 deletions docs/bundler/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -507,10 +507,12 @@ When `true`, the bundler enables code splitting. When multiple entrypoints impor

```ts entry-a.ts icon="/icons/typescript.svg"
import { shared } from "./shared.ts";
console.log(shared);
```

```ts entry-b.ts icon="/icons/typescript.svg"
import { shared } from "./shared.ts";
console.log(shared);
```

```ts shared.ts icon="/icons/typescript.svg"
Expand Down Expand Up @@ -538,7 +540,7 @@ To bundle `entry-a.ts` and `entry-b.ts` with code-splitting enabled:
</Tab>
</Tabs>

Running this build results in the following files:
Running this build with the JavaScript API results in the following files:

```text title="file system" icon="folder-tree"
.
Expand All @@ -548,10 +550,10 @@ Running this build results in the following files:
└── out
├── entry-a.js
├── entry-b.js
└── chunk-2fce6291bf86559d.js
└── chunk-dqmx6gc8.js
```

The generated `chunk-2fce6291bf86559d.js` file contains the shared code. To avoid collisions, the file name includes a content hash by default. Customize this with [`naming`](#naming).
The generated `chunk-dqmx6gc8.js` file contains the shared code. To avoid collisions, the file name includes a content hash by default. The `bun build` CLI names this chunk `entry-a-t268ez5g.js` instead of `chunk-<hash>.js`. Customize this with [`naming`](#naming).

### plugins

Expand Down Expand Up @@ -1099,7 +1101,7 @@ var logo = "https://cdn.example.com/logo-a7305bdef.svg";

### define

A map of global identifiers to be replaced at build time. Keys of this object are identifier names, and values are JSON strings, identifiers, or property paths that are inlined.
A map of global identifiers to be replaced at build time. Keys of this object are identifiers or dotted property paths such as `process.env.NODE_ENV`, and values are JSON strings, identifiers, or property paths that are inlined.

<Tabs>
<Tab title="JavaScript">
Expand Down
2 changes: 1 addition & 1 deletion docs/bundler/macros.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ The first import resolves to `./node_modules/my-package/index.js`; Bun's bundler

When Bun's transpiler sees a macro import, it calls the function using Bun's JavaScript runtime and converts the return value into an AST node.

Macros run synchronously in the transpiler during the visiting phase, after the transpiler parses the file into an AST. They run in the order they are imported. The transpiler waits for each macro to finish before continuing, and awaits any Promise a macro returns.
Macros run synchronously in the transpiler during the visiting phase, after the transpiler parses the file into an AST. They run in the order their calls appear in the file; the transpiler does not load or run a macro module until it reaches a call to one of its exports. The transpiler waits for each macro to finish before continuing, and awaits any Promise a macro returns.

Bun's bundler is multi-threaded, so macros execute in parallel in multiple spawned JavaScript "workers".

Expand Down
10 changes: 5 additions & 5 deletions docs/bundler/minifier.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -685,15 +685,15 @@ function calculateSum(firstNumber, secondNumber) {
```

```js Output
function a(b,c){const d=b+c;return d}
function n(t,u){const c=t+u;return c}
```

**Naming strategy:**

- Most frequently used identifiers get the shortest names (a, b, c...)
- Single letters: a-z (26 names)
- Double letters: aa-zz (676 names)
- Triple letters and beyond as needed
- Most frequently used identifiers get the shortest names; Bun orders the alphabet by how often each character appears in the source, so the first names are usually letters like t, e and n rather than a, b, c
- Single characters: a-z, A-Z and `_` (53 names; `$` alone is reserved)
- Two characters: the second character can also be a digit (up to 3,456 names)
- Three characters and beyond as needed

**Preserved identifiers:**

Expand Down
25 changes: 14 additions & 11 deletions docs/bundler/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,18 @@ onStart(callback: () => void | Promise<void>): void;
Registers a callback that runs when the bundler starts a new bundle.

```ts title="index.ts" icon="/icons/typescript.svg"
import { plugin } from "bun";

plugin({
name: "onStart example",

setup(build) {
build.onStart(() => {
console.log("Bundle started!");
});
},
await Bun.build({
entrypoints: ["./app.ts"],
plugins: [
{
name: "onStart example",
setup(build) {
build.onStart(() => {
console.log("Bundle started!");
});
},
},
],
});
```

Expand Down Expand Up @@ -234,7 +236,8 @@ import type { BunPlugin } from "bun";
const envPlugin: BunPlugin = {
name: "env plugin",
setup(build) {
build.onLoad({ filter: /env/, namespace: "file" }, args => {
build.onResolve({ filter: /^env$/ }, () => ({ path: "env", namespace: "env" }));
build.onLoad({ filter: /.*/, namespace: "env" }, args => {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
claude[bot] marked this conversation as resolved.
return {
contents: `export default ${JSON.stringify(process.env)}`,
loader: "js",
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/ecosystem/neon-serverless-postgres.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const sql = neon(process.env.DATABASE_URL!);

const rows = await sql`SELECT version()`;

console.log(rows[0].version);
console.log(rows[0]?.version);
```

---
Expand Down
49 changes: 39 additions & 10 deletions docs/guides/test/todo-tests.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ mode: center
To remind yourself to write a test later, use the `test.todo` function. An implementation isn't required.

```ts test.test.ts icon="/icons/typescript.svg"
import { test, expect } from "bun:test";
import { test } from "bun:test";

// write this later
test.todo("unimplemented feature");
test.todo("parses durations like 1h30m");
```

---
Expand All @@ -23,42 +23,71 @@ bun test

```txt
test.test.ts:
unimplemented feature
parses durations like 1h30m

0 pass
1 todo
0 fail
Ran 1 test across 1 file. [74.00ms]
Ran 1 test across 1 file. [6.00ms]
```

---

You can provide a test implementation.
You can also write the test before the code it tests exists. Here `parseDuration` is still a stub, and the todo test records what it should eventually do. `bun test` reports this test as `todo` too, without running its body.

```ts
```ts test.test.ts icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test.todo("unimplemented feature", () => {
expect(Bun.isAwesome()).toBe(true);
// Not written yet; the todo test below says what it should do.
function parseDuration(input: string): number {
throw new Error("not implemented");
}

test.todo("parses durations like 1h30m", () => {
expect(parseDuration("1h30m")).toBe(5400);
});
```

---

Bun doesn't run the implementation unless you pass the `--todo` flag. With `--todo`, the test runs and is _expected to fail_. If a todo test passes, `bun test` returns a non-zero exit code.
Pass `--todo` to run the bodies of todo tests. A todo test is _expected to fail_: while `parseDuration` is unimplemented, Bun prints the error, still counts the test as `todo`, and exits with code `0`.

```sh terminal icon="terminal"
bun test --todo
```

```txt
test.test.ts:
✗ unimplemented feature
1 | import { test, expect } from "bun:test";
2 |
3 | // Not written yet; the todo test below says what it should do.
4 | function parseDuration(input: string): number {
5 | throw new Error("not implemented");
^
error: not implemented
at parseDuration (/path/to/test.test.ts:5:36)
at <anonymous> (/path/to/test.test.ts:9:10)
✎ parses durations like 1h30m [0.16ms]

0 pass
1 todo
0 fail
Ran 1 test across 1 file. [5.00ms]
```

---

Once you implement `parseDuration` and the body passes, `bun test --todo` reports the test as a failure and exits with a non-zero code. That is the signal to remove `.todo` and turn it into a regular test.

```txt
test.test.ts:
✗ parses durations like 1h30m [0.19ms]
^ this test is marked as todo but passes. Remove `.todo` if tested behavior now works

0 pass
1 fail
1 expect() calls
Ran 1 test across 1 file. [5.00ms]
$ echo $?
1 # this is the exit code of the previous command
```
Expand Down
2 changes: 1 addition & 1 deletion docs/pm/cli/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ rm -rf node_modules
bun install --backend copyfile
```

**`symlink`** is typically only used for `file:` dependencies internally. To prevent infinite loops, it skips symlinking the `node_modules` folder.
**`symlink`** is typically only used for `file:` dependencies internally (for example `file:../foo` and transitive `file:` dependencies). `link:` dependencies do not use this backend; Bun installs them as a single symlink to the linked directory.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass `--preserve-symlinks` to `node` or `bun`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).

Expand Down
2 changes: 1 addition & 1 deletion docs/pm/global-cache.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Configure this with the `--backend` flag, which all of Bun's package management
- **`clonefile`**: Default on macOS.
- **`clonefile_each_dir`**: Similar to `clonefile`, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than `clonefile`.
- **`copyfile`**: The fallback used when any of the above fail. It is the slowest option. On macOS, it uses `fcopyfile()`; on Linux it uses `copy_file_range()`.
- **`symlink`**: Used only for `file:` dependencies. To prevent infinite loops, it skips symlinking the `node_modules` folder.
- **`symlink`**: Symlinks each file instead of copying it. Only hoisted installs use it: `--backend=symlink` applies it to every package (macOS and Linux; Windows ignores the flag); without the flag, Bun uses it only for `file:` dependencies outside the project directory (for example `file:../foo`) and for transitive `file:` dependencies.

If you install with `--backend=symlink`, Node.js doesn't resolve node_modules of dependencies unless each dependency has its own `node_modules` folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).

Expand Down
2 changes: 1 addition & 1 deletion docs/pm/isolated-installs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ rm -rf node_modules pnpm-lock.yaml
bun install --linker isolated
```

The main difference is that Bun keeps its store inside the project's `node_modules/.bun/` by default, while pnpm uses a global store with symlinks. With [`install.globalStore`](#global-virtual-store) enabled, Bun uses a global store as well.
The layouts are close: Bun hardlinks (clones on macOS) packages from its [global cache](/pm/global-cache) into a per-project store, `node_modules/.bun/`, and symlinks top-level `node_modules` entries into it. With [`install.globalStore`](#global-virtual-store) enabled, those store entries become symlinks into a global virtual store instead.

## When to use isolated installs

Expand Down
11 changes: 7 additions & 4 deletions docs/project/bindgen.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ declare function add(a: number, b: number = -1): number;

The code generator emits a C++ thunk that validates and coerces the JS
arguments, then calls the Rust implementation. On the Rust side bindgen emits
nothing: the matching module is hand-written in
`src/jsc/bindings/GeneratedBindings.rs` and is reachable as
`crate::r#gen::<basename>` (for `bindgen_test.bind.ts`, that's
nothing; both the dispatch shim the thunk calls
(`bindgen_Bindgen_test_dispatchAdd1` in `src/runtime/hw_exports.rs`, which
calls `add`) and the `create_*_callback` module in
`src/jsc/bindings/GeneratedBindings.rs` are hand-written. The module is
reachable as `crate::r#gen::<basename>` (for `bindgen_test.bind.ts`, that's
`crate::r#gen::bindgen_test`). To construct a `JSFunction` wrapping the
native implementation, use `generated::create_add_callback(global)`:

Expand All @@ -77,7 +79,8 @@ let js_fn: JSValue = generated::create_add_callback(global);
```

In JS files in `src/js/`, `$bindgenFn("bindgen_test.bind.ts", "add")` returns
a handle to the implementation.
a handle to the implementation, through a hand-written
`js2native_bindgen_<basename>_<fn>` export in `src/runtime/hw_exports.rs`.

Exported bindgen functions are snake_cased on the Rust side
(`requiredAndOptionalArg` → `required_and_optional_arg`). The hand-written
Expand Down
Loading