Files
bun.sh/docs/test/lifecycle.mdx
robobun b876938f6d docs: update documentation for Bun v1.3.2 features (#24503)
## Summary
Updates documentation for all major features and changes introduced in
Bun v1.3.2 blog post.

## Changes

### Package Manager
-  Document `configVersion` system for controlling default linker
behavior
-  Clarify that "existing projects (made pre-v1.3.2)" use hoisted
installs for backward compatibility
-  Add smart postinstall script optimization with environment variable
flags
-  Document improved Git dependency resolution with HTTP tarball
optimization
-  Add `bun list` alias for `bun pm ls`

### Testing
-  Document new `onTestFinished` lifecycle hook with simple example
-  Add to lifecycle hooks table in test documentation

### Runtime & Performance
-  Add CPU profiling with `--cpu-prof` flag documentation
-  Place after memory usage section for better flow

### WebSockets
-  Add `subscriptions` getter to existing pub/sub example
-  Add TypeScript reference for the subscriptions property

## Documentation Improvements
All documentation now consistently:
- Uses "made pre-v1.3.2" to clarify existing project behavior
- Simplifies default linker explanations with clear references to
`/docs/pm/isolated-installs`
- Uses `/docs/pm/isolated-installs` for all internal references
- Avoids confusing technical details in favor of user-friendly summaries

## Files Modified
- `docs/guides/install/add-git.mdx` - Added GitHub tarball optimization
note
- `docs/pm/cli/install.mdx` - Added installation strategies and smart
postinstall docs
- `docs/pm/cli/pm.mdx` - Added bun list alias
- `docs/pm/isolated-installs.mdx` - Updated default behavior section
with configVersion table
- `docs/project/benchmarking.mdx` - Added CPU profiling section
- `docs/runtime/bunfig.mdx` - Clarified install.linker defaults
- `docs/runtime/http/websockets.mdx` - Added subscriptions to example
and TypeScript interface
- `docs/test/lifecycle.mdx` - Added onTestFinished hook documentation

## Diff

````diff
diff --git a/docs/guides/install/add-git.mdx b/docs/guides/install/add-git.mdx
index 70950e1a63..7f8f3c8d81 100644
--- a/docs/guides/install/add-git.mdx
+++ b/docs/guides/install/add-git.mdx
@@ -33,6 +33,8 @@ bun add git@github.com:lodash/lodash.git
 bun add github:colinhacks/zod
 ```
 
+**Note:** GitHub dependencies download via HTTP tarball when possible for faster installation.
+
 ---
 
 See [Docs > Package manager](https://bun.com/docs/cli/install) for complete documentation of Bun's package manager.
diff --git a/docs/pm/cli/install.mdx b/docs/pm/cli/install.mdx
index 7affb62646..dde268b7e5 100644
--- a/docs/pm/cli/install.mdx
+++ b/docs/pm/cli/install.mdx
@@ -88,6 +88,13 @@ Lifecycle scripts will run in parallel during installation. To adjust the maximu
 bun install --concurrent-scripts 5
 ```
 
+Bun automatically optimizes postinstall scripts for popular packages (like `esbuild`, `sharp`, etc.) by determining which scripts need to run. To disable these optimizations:
+
+```bash terminal icon="terminal"
+BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER=1 bun install
+BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1 bun install
+```
+
 ---
 
 ## Workspaces
@@ -231,7 +238,7 @@ Bun supports installing dependencies from Git, GitHub, and local or remotely-hos
 
 Bun supports two package installation strategies that determine how dependencies are organized in `node_modules`:
 
-### Hoisted installs (default for single projects)
+### Hoisted installs
 
 The traditional npm/Yarn approach that flattens dependencies into a shared `node_modules` directory:
 
@@ -249,7 +256,15 @@ bun install --linker isolated
 
 Isolated installs create a central package store in `node_modules/.bun/` with symlinks in the top-level `node_modules`. This ensures packages can only access their declared dependencies.
 
-For complete documentation on isolated installs, refer to [Package manager > Isolated installs](/pm/isolated-installs).
+### Default strategy
+
+The default linker strategy depends on whether you're starting fresh or have an existing project:
+
+- **New workspaces/monorepos**: `isolated` (prevents phantom dependencies)
+- **New single-package projects**: `hoisted` (traditional npm behavior)
+- **Existing projects (made pre-v1.3.2)**: `hoisted` (preserves backward compatibility)
+
+The default is controlled by a `configVersion` field in your lockfile. For a detailed explanation, see [Package manager > Isolated installs](/docs/pm/isolated-installs).
 
 ---
 
@@ -319,8 +334,7 @@ dryRun = false
 concurrentScripts = 16 # (cpu count or GOMAXPROCS) x2
 
 # installation strategy: "hoisted" or "isolated"
-# default: "hoisted" (for single-project projects)
-# default: "isolated" (for monorepo projects)
+# default varies by project type - see /docs/pm/isolated-installs
 linker = "hoisted"
 
 
diff --git a/docs/pm/cli/pm.mdx b/docs/pm/cli/pm.mdx
index fc297753d3..9c8faa7da1 100644
--- a/docs/pm/cli/pm.mdx
+++ b/docs/pm/cli/pm.mdx
@@ -115,6 +115,8 @@ To print a list of installed dependencies in the current project and their resol
 
 ```bash terminal icon="terminal"
 bun pm ls
+# or
+bun list
 ```
 
 ```txt
@@ -130,6 +132,8 @@ To print all installed dependencies, including nth-order dependencies.
 
 ```bash terminal icon="terminal"
 bun pm ls --all
+# or
+bun list --all
 ```
 
 ```txt
diff --git a/docs/pm/isolated-installs.mdx b/docs/pm/isolated-installs.mdx
index 73c6748b15..17afe02fe1 100644
--- a/docs/pm/isolated-installs.mdx
+++ b/docs/pm/isolated-installs.mdx
@@ -5,7 +5,7 @@ description: "Strict dependency isolation similar to pnpm's approach"
 
 Bun provides an alternative package installation strategy called **isolated installs** that creates strict dependency isolation similar to pnpm's approach. This mode prevents phantom dependencies and ensures reproducible, deterministic builds.
 
-This is the default installation strategy for monorepo projects.
+This is the default installation strategy for **new** workspace/monorepo projects (with `configVersion = 1` in the lockfile). Existing projects continue using hoisted installs unless explicitly configured.
 
 ## What are isolated installs?
 
@@ -43,8 +43,23 @@ linker = "isolated"
 
 ### Default behavior
 
-- For monorepo projects, Bun uses the **isolated** installation strategy by default.
-- For single-project projects, Bun uses the **hoisted** installation strategy by default.
+The default linker strategy depends on your project's lockfile `configVersion`:
+
+| `configVersion` | Using workspaces? | Default Linker |
+| --------------- | ----------------- | -------------- |
+| `1`             |                 | `isolated`     |
+| `1`             |                 | `hoisted`      |
+| `0`             |                 | `hoisted`      |
+| `0`             |                 | `hoisted`      |
+
+**New projects**: Default to `configVersion = 1`. In workspaces, v1 uses the isolated linker by default; otherwise it uses hoisted linking.
+
+**Existing Bun projects (made pre-v1.3.2)**: If your existing lockfile doesn't have a version yet, Bun sets `configVersion = 0` when you run `bun install`, preserving the previous hoisted linker default.
+
+**Migrations from other package managers**:
+
+- From pnpm: `configVersion = 1` (using isolated installs in workspaces)
+- From npm or yarn: `configVersion = 0` (using hoisted installs)
 
 You can override the default behavior by explicitly specifying the `--linker` flag or setting it in your configuration file.
 
diff --git a/docs/project/benchmarking.mdx b/docs/project/benchmarking.mdx
index 1263a06729..2ab8bcafc8 100644
--- a/docs/project/benchmarking.mdx
+++ b/docs/project/benchmarking.mdx
@@ -216,3 +216,26 @@ numa nodes:       1
    elapsed:       0.068 s
    process: user: 0.061 s, system: 0.014 s, faults: 0, rss: 57.4 MiB, commit: 64.0 MiB
 ```
+
+## CPU profiling
+
+Profile JavaScript execution to identify performance bottlenecks with the `--cpu-prof` flag.
+
+```sh terminal icon="terminal"
+bun --cpu-prof script.js
+```
+
+This generates a `.cpuprofile` file you can open in Chrome DevTools (Performance tab → Load profile) or VS Code's CPU profiler.
+
+### Options
+
+```sh terminal icon="terminal"
+bun --cpu-prof --cpu-prof-name my-profile.cpuprofile script.js
+bun --cpu-prof --cpu-prof-dir ./profiles script.js
+```
+
+| Flag                         | Description          |
+| ---------------------------- | -------------------- |
+| `--cpu-prof`                 | Enable profiling     |
+| `--cpu-prof-name <filename>` | Set output filename  |
+| `--cpu-prof-dir <dir>`       | Set output directory |
diff --git a/docs/runtime/bunfig.mdx b/docs/runtime/bunfig.mdx
index 91005c1607..5b7fe49823 100644
--- a/docs/runtime/bunfig.mdx
+++ b/docs/runtime/bunfig.mdx
@@ -497,9 +497,9 @@ print = "yarn"
 
 ### `install.linker`
 
-Configure the default linker strategy. Default `"hoisted"` for single-project projects, `"isolated"` for monorepo projects.
+Configure the linker strategy for installing dependencies. Defaults to `"isolated"` for new workspaces, `"hoisted"` for new single-package projects and existing projects (made pre-v1.3.2).
 
-For complete documentation refer to [Package manager > Isolated installs](/pm/isolated-installs).
+For complete documentation refer to [Package manager > Isolated installs](/docs/pm/isolated-installs).
 
 ```toml title="bunfig.toml" icon="settings"
 [install]
diff --git a/docs/runtime/http/websockets.mdx b/docs/runtime/http/websockets.mdx
index b33f37c29f..174043200d 100644
--- a/docs/runtime/http/websockets.mdx
+++ b/docs/runtime/http/websockets.mdx
@@ -212,6 +212,9 @@ const server = Bun.serve({
       // this is a group chat
       // so the server re-broadcasts incoming message to everyone
       server.publish("the-group-chat", `${ws.data.username}: ${message}`);
+
+      // inspect current subscriptions
+      console.log(ws.subscriptions); // ["the-group-chat"]
     },
     close(ws) {
       const msg = `${ws.data.username} has left the chat`;
@@ -393,6 +396,7 @@ interface ServerWebSocket {
   readonly data: any;
   readonly readyState: number;
   readonly remoteAddress: string;
+  readonly subscriptions: string[];
   send(message: string | ArrayBuffer | Uint8Array, compress?: boolean): number;
   close(code?: number, reason?: string): void;
   subscribe(topic: string): void;
diff --git a/docs/test/lifecycle.mdx b/docs/test/lifecycle.mdx
index 6427175df6..3837f0e948 100644
--- a/docs/test/lifecycle.mdx
+++ b/docs/test/lifecycle.mdx
@@ -6,11 +6,12 @@ description: "Learn how to use beforeAll, beforeEach, afterEach, and afterAll li
 The test runner supports the following lifecycle hooks. This is useful for loading test fixtures, mocking data, and configuring the test environment.
 
 | Hook             | Description                                                |
-| ------------ | --------------------------- |
+| ---------------- | ---------------------------------------------------------- |
 | `beforeAll`      | Runs once before all tests.                                |
 | `beforeEach`     | Runs before each test.                                     |
 | `afterEach`      | Runs after each test.                                      |
 | `afterAll`       | Runs once after all tests.                                 |
+| `onTestFinished` | Runs after a single test finishes (after all `afterEach`). |
 
 ## Per-Test Setup and Teardown
 
@@ -90,6 +91,23 @@ describe("test group", () => {
 });
 ```
 
+### `onTestFinished`
+
+Use `onTestFinished` to run a callback after a single test completes. It runs after all `afterEach` hooks.
+
+```ts title="test.ts" icon="/icons/typescript.svg"
+import { test, onTestFinished } from "bun:test";
+
+test("cleanup after test", () => {
+  onTestFinished(() => {
+    // runs after all afterEach hooks
+    console.log("test finished");
+  });
+});
+```
+
+Not supported in concurrent tests; use `test.serial` instead.
+
 ## Global Setup and Teardown
 
 To scope the hooks to an entire multi-file test run, define the hooks in a separate file.
````

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Michael H <git@riskymh.dev>
Co-authored-by: Lydia Hallie <lydiajuliettehallie@gmail.com>
2025-11-10 18:18:07 -08:00

367 lines
8.6 KiB
Plaintext

---
title: "Lifecycle hooks"
description: "Learn how to use beforeAll, beforeEach, afterEach, and afterAll lifecycle hooks in Bun tests"
---
The test runner supports the following lifecycle hooks. This is useful for loading test fixtures, mocking data, and configuring the test environment.
| Hook | Description |
| ---------------- | ---------------------------------------------------------- |
| `beforeAll` | Runs once before all tests. |
| `beforeEach` | Runs before each test. |
| `afterEach` | Runs after each test. |
| `afterAll` | Runs once after all tests. |
| `onTestFinished` | Runs after a single test finishes (after all `afterEach`). |
## Per-Test Setup and Teardown
Perform per-test setup and teardown logic with `beforeEach` and `afterEach`.
```ts title="test.ts" icon="/icons/typescript.svg"
import { beforeEach, afterEach, test } from "bun:test";
beforeEach(() => {
console.log("running test.");
});
afterEach(() => {
console.log("done with test.");
});
// tests...
test("example test", () => {
// This test will have beforeEach run before it
// and afterEach run after it
});
```
## Per-Scope Setup and Teardown
Perform per-scope setup and teardown logic with `beforeAll` and `afterAll`. The scope is determined by where the hook is defined.
### Scoped to a Describe Block
To scope the hooks to a particular describe block:
```ts title="test.ts" icon="/icons/typescript.svg"
import { describe, beforeAll, afterAll, test } from "bun:test";
describe("test group", () => {
beforeAll(() => {
// setup for this describe block
console.log("Setting up test group");
});
afterAll(() => {
// teardown for this describe block
console.log("Tearing down test group");
});
test("test 1", () => {
// test implementation
});
test("test 2", () => {
// test implementation
});
});
```
### Scoped to a Test File
To scope the hooks to an entire test file:
```ts title="test.ts" icon="/icons/typescript.svg"
import { describe, beforeAll, afterAll, test } from "bun:test";
beforeAll(() => {
// setup for entire file
console.log("Setting up test file");
});
afterAll(() => {
// teardown for entire file
console.log("Tearing down test file");
});
describe("test group", () => {
test("test 1", () => {
// test implementation
});
});
```
### `onTestFinished`
Use `onTestFinished` to run a callback after a single test completes. It runs after all `afterEach` hooks.
```ts title="test.ts" icon="/icons/typescript.svg"
import { test, onTestFinished } from "bun:test";
test("cleanup after test", () => {
onTestFinished(() => {
// runs after all afterEach hooks
console.log("test finished");
});
});
```
Not supported in concurrent tests; use `test.serial` instead.
## Global Setup and Teardown
To scope the hooks to an entire multi-file test run, define the hooks in a separate file.
```ts title="setup.ts" icon="/icons/typescript.svg"
import { beforeAll, afterAll } from "bun:test";
beforeAll(() => {
// global setup
console.log("Global test setup");
// Initialize database connections, start servers, etc.
});
afterAll(() => {
// global teardown
console.log("Global test teardown");
// Close database connections, stop servers, etc.
});
```
Then use `--preload` to run the setup script before any test files.
```bash terminal icon="terminal"
bun test --preload ./setup.ts
```
To avoid typing `--preload` every time you run tests, it can be added to your `bunfig.toml`:
```toml title="bunfig.toml" icon="settings"
[test]
preload = ["./setup.ts"]
```
## Practical Examples
### Database Setup
```ts title="database-setup.ts" icon="/icons/typescript.svg"
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
import { createConnection, closeConnection, clearDatabase } from "./db";
let connection;
beforeAll(async () => {
// Connect to test database
connection = await createConnection({
host: "localhost",
database: "test_db",
});
});
afterAll(async () => {
// Close database connection
await closeConnection(connection);
});
beforeEach(async () => {
// Start with clean database for each test
await clearDatabase(connection);
});
```
### API Server Setup
```ts title="server-setup.ts" icon="/icons/typescript.svg"
import { beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";
let server;
beforeAll(async () => {
// Start test server
server = await startServer({
port: 3001,
env: "test",
});
});
afterAll(async () => {
// Stop test server
await stopServer(server);
});
```
### Mock Setup
```ts title="mock-setup.ts" icon="/icons/typescript.svg"
import { beforeEach, afterEach } from "bun:test";
import { mock } from "bun:test";
beforeEach(() => {
// Set up common mocks
mock.module("./api-client", () => ({
fetchUser: mock(() => Promise.resolve({ id: 1, name: "Test User" })),
createUser: mock(() => Promise.resolve({ id: 2 })),
}));
});
afterEach(() => {
// Clear all mocks after each test
mock.restore();
});
```
## Async Lifecycle Hooks
All lifecycle hooks support async functions:
```ts title="test.ts" icon="/icons/typescript.svg"
import { beforeAll, afterAll, test } from "bun:test";
beforeAll(async () => {
// Async setup
await new Promise(resolve => setTimeout(resolve, 100));
console.log("Async setup complete");
});
afterAll(async () => {
// Async teardown
await new Promise(resolve => setTimeout(resolve, 100));
console.log("Async teardown complete");
});
test("async test", async () => {
// Test will wait for beforeAll to complete
await expect(Promise.resolve("test")).resolves.toBe("test");
});
```
## Nested Hooks
Hooks can be nested and will run in the appropriate order:
```ts title="test.ts" icon="/icons/typescript.svg"
import { describe, beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test";
beforeAll(() => console.log("File beforeAll"));
afterAll(() => console.log("File afterAll"));
describe("outer describe", () => {
beforeAll(() => console.log("Outer beforeAll"));
beforeEach(() => console.log("Outer beforeEach"));
afterEach(() => console.log("Outer afterEach"));
afterAll(() => console.log("Outer afterAll"));
describe("inner describe", () => {
beforeAll(() => console.log("Inner beforeAll"));
beforeEach(() => console.log("Inner beforeEach"));
afterEach(() => console.log("Inner afterEach"));
afterAll(() => console.log("Inner afterAll"));
test("nested test", () => {
console.log("Test running");
});
});
});
```
```txt
// Output order:
// File beforeAll
// Outer beforeAll
// Inner beforeAll
// Outer beforeEach
// Inner beforeEach
// Test running
// Inner afterEach
// Outer afterEach
// Inner afterAll
// Outer afterAll
// File afterAll
```
## Error Handling
If a lifecycle hook throws an error, it will affect test execution:
```ts title="test.ts" icon="/icons/typescript.svg"
import { beforeAll, test } from "bun:test";
beforeAll(() => {
// If this throws, all tests in this scope will be skipped
throw new Error("Setup failed");
});
test("this test will be skipped", () => {
// This won't run because beforeAll failed
});
```
For better error handling:
```ts title="test.ts" icon="/icons/typescript.svg"
import { beforeAll, test, expect } from "bun:test";
beforeAll(async () => {
try {
await setupDatabase();
} catch (error) {
console.error("Database setup failed:", error);
throw error; // Re-throw to fail the test suite
}
});
```
## Best Practices
### Keep Hooks Simple
```ts title="test.ts" icon="/icons/typescript.svg"
// Good: Simple, focused setup
beforeEach(() => {
clearLocalStorage();
resetMocks();
});
// Avoid: Complex logic in hooks
beforeEach(async () => {
// Too much complex logic makes tests hard to debug
const data = await fetchComplexData();
await processData(data);
await setupMultipleServices(data);
});
```
### Use Appropriate Scope
```ts title="test.ts" icon="/icons/typescript.svg"
// Good: File-level setup for shared resources
beforeAll(async () => {
await startTestServer();
});
// Good: Test-level setup for test-specific state
beforeEach(() => {
user = createTestUser();
});
```
### Clean Up Resources
```ts title="test.ts" icon="/icons/typescript.svg"
import { afterAll, afterEach } from "bun:test";
afterEach(() => {
// Clean up after each test
document.body.innerHTML = "";
localStorage.clear();
});
afterAll(async () => {
// Clean up expensive resources
await closeDatabase();
await stopServer();
});
```