Bun Release: Performance Boosts, CPU Profiling, and Enhanced Stability
Explore the latest Bun release featuring significant performance improvements for `bun install`, a new CPU profiling flag, refined dependency resolution, and numerous bug fixes across Node.js compatibility, N-API, and testing.
This release addresses 287 issues, resulting in 324 positive impacts (π).
Installation
To Install Bun
# Using curl (Linux/macOS)
curl -fsSL https://bun.sh/install | bash
# Using npm
npm install -g bun
# Using PowerShell (Windows)
powershell -c "irm bun.sh/install.ps1|iex"
# Using Scoop (Windows)
scoop install bun
# Using Homebrew (macOS/Linux)
brew tap oven-sh/bun
brew install bun
# Using Docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
To Upgrade Bun
bun upgrade
Hoisted Installs Restored as Default
In Bun 1.3.0, isolated installs became the default for workspaces, which aimed to eliminate phantom dependencies and improve install speed. However, this caused issues for existing monorepos relying on shared dependencies.
With Bun 1.3.2, isolated installs are now only the default for new projects. Existing workspaces will revert to using hoisted installs unless explicitly configured otherwise.
To Keep Using Isolated Installs in Existing Workspaces/Monorepos
You can configure this in your bunfig.toml:
[install]
# Explicitly set the linker to isolated
linker = "isolated"
Alternatively, use the --linker=isolated flag:
bun install --linker=isolated
New projects using workspaces (or those without a bun.lock/bun.lockb file) will continue to default to isolated installs.
configVersion Stabilizes Install Defaults
To prevent disruptive changes in future Bun upgrades, bun install now writes a configVersion to your bun.lock or bun.lockb file. This allows future default configuration changes without impacting existing projects.
Here's how it works:
- New projects: Default to
configVersion = 1(v1). In workspaces, v1 uses the isolated linker by default; otherwise, it uses hoisted linking. - Existing Bun projects: If your lockfile doesn't have a version, Bun sets
configVersion = 0(v0) when you runbun install, preserving the previous hoisted linker default. - Migrations from other package managers:
- From pnpm:
configVersion = 1(v1) - From npm or yarn:
configVersion = 0(v0)
- From pnpm:
// Example in bun.lock
{
// New projects:
"configVersion": 1,
// Existing projects without a version (after running `bun install`):
"configVersion": 0
}
The default linker behavior is now determined by a combination of configVersion and whether workspaces are used:
Lockfile configVersion | Using Workspaces? | Default Linker |
|---|---|---|
1 | β | isolated |
1 | β | hoisted |
0 | β | hoisted |
0 | β | hoisted |
Faster bun install
Projects depending on popular libraries like esbuild or sharp will now experience faster install times.
Future Bun versions will also feature smarter postinstall script execution. For example, a repository with Next.js and Vite saw bun install become 6x faster.
CPU Profiling with --cpu-prof
Bun now supports CPU profiling for any script using the --cpu-prof flag. This feature records detailed information about program execution, helping you identify performance bottlenecks and optimize critical code paths.
The next version of Bun will generate CPU profiles compatible with Chrome DevTools, powered by JavaScriptCore's sampling profiler.
Profiles are saved in the Chrome DevTools-compatible .cpuprofile format and can be opened directly in Chrome DevTools (Performance tab) or VS Code's CPU profiler. Sampling runs at 1ms for fine-grained insights.
CPU Profiling Flags
| Flag | Description |
|---|---|
--cpu-prof | Enables profiling |
--cpu-prof-name <filename> | Sets the output filename |
--cpu-prof-dir <dir> | Sets the output directory |
Example Usage
// script.js
function fib(n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
console.log(fib(35)); // some CPU work
To run this script with CPU profiling:
bun --cpu-prof script.js
bun --cpu-prof --cpu-prof-name my-profile.cpuprofile script.js
bun --cpu-prof --cpu-prof-dir ./profiles script.js
Open the generated .cpuprofile file in Chrome DevTools (Performance tab) -> Load profile.
bun:test onTestFinished Hook
bun:test now includes a new onTestFinished(fn) hook. This hook executes at the very end of a test, after all afterEach hooks have completed. It's ideal for cleanup or assertions that must occur post-all other per-test hooks.
- Runs only inside a test (not in
describeor preload). - Supports async and done-style callbacks.
- Not supported in concurrent tests; use
test.serialor removetest.concurrent.
Example
import { test, afterEach, onTestFinished, expect } from "bun:test";
test("runs after afterEach", () => {
const calls = [];
afterEach(() => {
calls.push("afterEach");
});
onTestFinished(() => {
calls.push("onTestFinished");
// afterEach has already run
expect(calls).toEqual(["afterEach", "onTestFinished"]);
});
// test body...
});
test.serial("async cleanup at the very end", async () => {
onTestFinished(async () => {
await new Promise((r) => setTimeout(r, 10));
// ...close DB connections, stop servers, etc.
});
// test body...
});
Thanks to @pfg for this contribution!
ServerWebSocket subscriptions Getter
The ServerWebSocket object now features a subscriptions getter. This getter returns a de-duplicated list of topics to which the connection is currently subscribed.
This makes it easier to inspect and manage per-connection state in pub/sub systems, such as debugging topic subscriptions or cleaning up resources when clients disconnect. When a socket closes, subscriptions automatically returns an empty array.
Example
const server = Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) return;
return new Response("Not a websocket");
},
websocket: {
open(ws) {
ws.subscribe("chat");
ws.subscribe("notifications");
console.log(ws.subscriptions); // ["chat", "notifications"]
ws.unsubscribe("chat");
console.log(ws.subscriptions); // ["notifications"]
},
close(ws) {
console.log(ws.subscriptions); // []
},
},
});
This enhancement significantly improves transparency and debugging for Bun's WebSocket pub/sub model.
Alpine 3.22 in Official Docker Images
Bun's official Alpine Linux Docker images now utilize Alpine 3.22 for both x64 and arm64(musl) builds. This update delivers the latest security patches, improved package compatibility, and a smaller base footprint.
Improved Git Dependency Resolution
bun install now offers enhanced support for npm-style hosted Git URLs and GitHub shorthands. GitHub repositories specified with custom protocol prefixes are now correctly identified and routed through the faster HTTP tarball download pathway.
package.json Example
{
"dependencies": {
// GitHub shorthand (now parsed correctly and downloaded via HTTP tarball)
"cool-lib": "github:owner/repo#v1.2.3",
// Different protocols resolve deterministically
"tooling-ssh": "git+ssh://git@github.com/owner/repo.git#main",
"tooling-https": "git+https://github.com/owner/repo.git#main"
}
}
Thanks to @markovejnovic for this contribution!
bun list Alias for bun pm ls
You can now list your dependency tree using the shorter, top-level command: bun list. This is a direct alias for bun pm ls and supports all the same flags, including --all for a full transitive view. This makes dependency inspection quicker and easier.
Example Usage
# List dependencies from the current lockfile (alias for `bun pm ls`)
bun list
# Show the full transitive dependency tree
bun list --all
spawnSync Now Runs on an Isolated Event Loop
Bun.spawnSync and child_process.spawnSync now execute on an isolated event loop, separate from the main process. This prevents JavaScript timers and microtasks from firing and interfering with the main process's stdin/stdout, aligning Bun's spawnSync behavior with Node.js. This change also makes timeouts reliable across all platforms, including Windows.
This addresses several bugs and stability issues in the previous implementation, such as execSync with vim "eating" the first character of keypresses. While this improves stability, please report any negative impacts if your project inadvertently relied on the previous behavior.
More Bug Fixes
Node.js Compatibility Improvements
EventEmitter: Fixed an error whenremoveAllListeners(type)was called from within an event handler with aremoveListenermeta-listener registered and no listeners for the target event. Behavior now matches Node.js (no error).ServerResponse.prototype.writableNeedDrain: Fixed incorrecttruereturn when the response had no handle, which causedfs.createReadStream().pipe(res)and other piped streams to pause indefinitely in middleware scenarios (e.g., Vite static file serving). Behavior now matches Node.js.process.mainModule: Setter/getter semantics now match Node.js.process.nextTick: Fixed crash when user code overridesprocess.nextTick. Bun now safely uses the overridden function internally.Buffer.isEncoding(''): Now correctly returnsfalseto match Node.js behavior.Module._resolveFilename: Now forwards theoptionsobject (includingoptions.paths) to overridden implementations and honorsoptions.pathswhen provided. This restores compatibility with Node-stylerequirehooks (e.g., Next.js 16) and fixes related build failures.Module._resolveFilename: Now validates thatoptions.pathsis an array and throwsERR_INVALID_ARG_TYPEotherwise, aligning with Node.js.process.dlopen: Fixed crash when passed non-object exports (null, undefined, or primitives). Bun now matches Node.jsToObjectsemantics, preventing segfaults when loading native addons.
N-API and Native Addons
napi_create_external_buffer: Now correctly handles empty inputs (null data and/or length 0) without throwing or creating a detached buffer. When length is 0, it returns a detachedArrayBuffermatching Node.js behavior.napi_get_buffer_info&napi_get_arraybuffer_info: Correctly report a null pointer and 0 length, andnapi_is_detached_arraybufferreturnstrue. This prevents crashes in addons and ensures zero-length buffers are created with safe finalization.- ThreadSafeFunction Finalizers: Fixed crashes in N-API when ThreadSafeFunction finalizers or async work deinitialized the environment during dispatch. Environment references are now safely retained.
- N-API Property Access: Now returns
undefinedfor missing properties and out-of-bounds elements, matching Node.js. - Numeric-String Keys: Handled consistently as index access across N-API property operations, aligning with Node.js.
napi_delete_property,napi_has_property,napi_has_own_property: Now return correct boolean results and propagate exceptions consistently.- Error Handling: Improved error handling across N-API property and element access.
better-sqlite3: Importingbetter-sqlite3now fails with a clear, actionable error (linking to the tracking issue and suggestingbun:sqliteas an alternative) instead of crashing.
HTTP/HTTPS and Networking
- TLS Verification: Restored use of the system CA trust store for TLS verification, fixing a 1.3.0 regression where some HTTPS requests failed with
UNABLE_TO_GET_ISSUER_CERT_LOCALLY. Bun now loads default OS CA paths again. - HTTP Server Idle: Fixed an issue where the HTTP server incorrectly marked a connection as idle after a write failure, causing longer than expected timeouts.
- WebSocket Upgrade CPU: Fixed an issue where upgrading WebSocket connections via the
wsmodule could consume 100% CPU in certain cases when it should be idling.
Fetch API
- Body Consumption: Fetch API methods now reject with
TypeErrorinstead ofErrorwhen the body has already been consumed (e.g., callingtext()thenjson()on the same Request/Response), aligning with the Fetch spec and matching Node/Deno behavior.
bun:test Bug Fixes
- Lifecycle Hooks Options: Fixed
bun:testlifecycle hooks (beforeAll,beforeEach,afterAll,afterEach) no longer throwing when called with a callback and options as the second argument.(callback, options)is now correctly parsed, supporting both object and numeric timeouts. - Snapshot Creation in CI:
bun:testnow emits clearer errors when snapshot creation is attempted in CI. Messages explicitly refer to creation, include the received value, and guide users to use--update-snapshotsor setCI=false. - Test Runner Crashes: Fixed rare crashes when a test prompted for a sudo password or left a dangling process.
expect(...).toThrowwith Async: Fixedexpect(...).toThrowwith an async function no longer crashing the test runner when the rejection occurs after the test timeout. The test now times out and reports a failure.vi.mockTypes: Fixedbun-typesforbun:testincorrectly typingvi.mock(...)asvi.module(...).vi.mockis now correctly typed.
bun build Bug Fixes
- Sourcemap Sorting: Fixed 2 different sourcemap sorting bugs.
CSS and Styling
- View-Transition Pseudo-elements: CSS view-transition pseudo-elements now support class selector arguments (e.g.,
::view-transition-old(.slide-out)), resolving "Unexpected token: ." errors during parsing/bundling. These selectors now parse, minify, and serialize correctly. @layerBlocks: CSS minifier now processes@layerblocks, ensuringcolor-schemerules receive required variable injections andprefers-color-schemefallbacks for browsers withoutlight-dark()support.
bun install Bug Fixes
bun update --interactive: Fixedbun update --interactive(including--latest) updatingpackage.jsonbut not installing selected updates. It now installs updated dependencies and refreshesnode_modules.npm:Alias Preservation:bun update --interactiveno longer stripsnpm:alias prefixes when updating dependencies inpackage.json. Aliases and range operators are preserved.- Optional Peer Dependencies: Fixed
bun installleaving optional peerDependencies unresolved in isolated installs, which caused inconsistent resolutions and type incompatibilities. Optional peers now resolve to an installed package when available. - Git Protocol Conflation:
bun installno longer conflatesgit+sshandgit+httpsreferences to the same repository; each specifier is resolved independently. - GitHub Custom Protocols: GitHub dependencies with custom protocol prefixes (e.g.,
git+https://github.com/owner/repo#v1.2.3) are now recognized as GitHub tag downloads, enabling the faster HTTP download path. Improved recognition of GitHub shorthand also increases reliability.
Runtime and Performance
- Global
~/.bunfig.toml: Fixed global~/.bunfig.tomlbeing loaded more than once, leading to duplicate configuration. Bun now guarantees it's loaded at most once per run. - MySQL OK Packet Parsing: Fixed crash when parsing MySQL OK packets with truncated or empty payloads.
Bun.CookieMap#delete: Fixed a crash inBun.CookieMap#deletein certain cases.- ANSI Color Detection: ANSI color support is now detected per stream (stdout vs stderr). This resolves missing colors in errors/crash reports and test diffs, and prevents misrendered box-drawing characters in interactive commands when the terminal doesn't support color.
- Interactive UI Box Drawing: Interactive UIs and installer output only use box-drawing characters when stdout supports ANSI, avoiding garbled tables in plain terminals.
- Hot Reload Terminal Clearing: Hot reload terminal-clearing logic respects stdout color capability.
- Test Framework Output Colors: Test framework output (expect diffs and matcher messages) consistently respects stderr color support.
- Truncated Stack Traces (glibc): Fixed crash reports on glibc-based Linux showing severely truncated stack traces. Bun now uses Zig's
std.debug.captureStackTracefor more complete traces.
Module Resolution
- ES Module
require()with TLA: Fixed requiring an ES module with top-level await viarequire()orimport.meta.requirethrowing and leaving a partially initialized module in the cache. The failed module is now evicted from the cache.
TypeScript and Types
- Web Stream Types: TypeScript types for
Blob,ReadableStream, andResponsenow includetext(),bytes(),json(),formData(), andarrayBuffer()convenience methods, resolving related type errors. Bun.spawn/spawnSyncOptions: TypeScript definitions forBun.spawnandspawnSyncnow include missing options and match runtime behavior (e.g.,detached,onDisconnect,lazy). Also clarified IPC lifecycle ordering withonExit.spawn/spawnSyncOption Shapes:spawn/spawnSyncoption shapes are unified viaBun.Spawn.BaseOptionsin the types;Spawn.OptionsObjectis deprecated.
Web Crypto
crypto.exportKey("jwk"): Fixedcrypto.exportKey("jwk")for EC private keys sometimes producing a shorter-than-required "d" parameter, violating RFC 7518. Bun now pads "d" to the correct length (e.g., P-256 (32 bytes), P-384 (48 bytes), P-521 (66 bytes)).
Thanks to Our Contributors!
@alii, @avarayr, @braden-w, @cirospaciari, @csvlad, @dylan-conway, @fraidev, @jarred-sumner, @lillious, @lydiahallie, @markovejnovic, @nektro, @nkxxll, @pfgithub, @riskymh, @robobun, @sosukesuzuki, @taylordotfish