Bun

Bun v1.1.43


Jarred Sumner · January 8, 2025

We're hiring systems engineers in San Francisco to build the future of JavaScript!

This release introduces 1st-class S3 support, HTML bundling, bun install --filter, V8 heap snapshots, bun install --lockfile-only, bun add --peer, 100% of node's path module Node.js test pass, 98% of zlib tests pass, Bun.file(path).stat(), Bun.file(path).delete(), along with many reliability improvements and bugfixes.

To install Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun

bun upgrade

1st-class S3 support

This release introduces 1st-class S3 Object Storage support in Bun.

Modern user-facing production servers frequently choose to store files in S3-like object storage services instead of the local POSIX filesystem. Decoupling storage capacity from compute capacity prevents an entire class of production reliability issues: low disk space, high p95 response times related to file system i/o, security issues related to persistent storage.

S3 is a de-facto standard that predates ES6 by a decade. The POSIX filesystem API was never designed for the internet age (non-blocking regular files aren't a thing! It's crazy!).

Bun's S3 API is designed to be feel similar to the web's Response and Blob APIs (like Bun's local filesystem APIs).

import { s3, write, S3Client } from "bun";

// Bun.s3 reads environment variables for credentials
// file() returns a lazy reference to a file on S3
const metadata = s3.file("123.json");

// Download from S3
const data = await metadata.json();
const text = await metadata.text();
const arrayBuffer = await metadata.arrayBuffer();
const uint8array = await metadata.bytes();

// Upload to S3
await metadata.write(JSON.stringify({ name: "John", age: 30 }));
await metadata.write(Bun.file("./123.json"));

// Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
  acl: "public-read",
  expiresIn: 60 * 60 * 24, // 1 day
});

// Delete the file
await metadata.delete();

We added this API to Bun because many servers don't use fs, they use S3.

You can use Bun's S3 client with any S3-compatible object storage service, such as:

  • AWS S3
  • Google Cloud Storage
  • DigitalOcean Spaces
  • Cloudflare R2
  • Backblaze B2
  • MinIO
  • ...and more

Learn more about Bun's S3 API in the docs.

HTML Bundler

Bun now natively supports bundling HTML files along with their associated JavaScript, CSS, and assets. This makes it easier to build static sites and web applications with Bun.

bun build --experimental-html --experimental-css ./index.html --outdir=dist


This bundles stylesheets, JavaScript, and all other assets. Multiple nested levels of JavaScript import and require get bundled into one file. CSS files are parsed and concatenated into one file (whether using @import or multiple <link> tags).

In Bun v1.2, we'll remove the --experimental-html and --experimental-css flags and they'll be enabled by default.

bun install --filter <pattern>

bun install gets --filter support. This lets you selectively install workspace packages within your monorepo.

Thanks to @dylan-conway for implementing this!

V8 Heap Snapshots

Bun now supports outputting V8 heap snapshots. This lets you use Chrome DevTools to inspect the heap of your Bun application. This wasn't previously possible before because Bun doesn't use V8 as the JavaScript engine. Bun uses JavaScriptCore. To make this work, we've added support for this in our fork of WebKit/JavaScriptCore.

Relatedly, we've also added support for v8.writeHeapSnapshot and v8.getHeapSnapshot in node:v8.

import { writeHeapSnapshot, getHeapSnapshot } from "node:v8";
const path = writeHeapSnapshot();
console.log(path); // Outputs the path to the generated heap snapshot

const snapshot = await Bun.file(path).json();
console.log(snapshot); // Outputs the parsed heap snapshot data


Errors show more properties

Previously, Bun hardcoded a small list of properties to display when printing top-level errors. Now we print all the own properties of the error.

Fixed:

❯ bun such.js
1 | const err = new Error("An example error");
                ^
error: An example error
  wow: "wow",
 such: "property",
 very: "information",

      at <file>.js:1:13

Previously, Bun would not show properties like wow, such, or very when displaying errors. Now it shows everything defined on the error object directly.

❯ bun-1.1.42 such.js
1 | const err = new Error("An example error");
                ^
error: An example error
      at <file>.js:1:13

bun run

-F is for --filter

bun run --filter='*pkg' <script> now supports the -F shorthand

bun run -F=<pattern> <script>
bun run --filter=<pattern> <script>
bun -F <pattern> <script>
bun -F=<pattern> <script>

--elide-lines=N controls filter output line length

The --elide-lines option in bun run --filter controls the number of output lines displayed when interactively running scripts in a terminal. This helps avoid missing errors in long scripts, while still showing the output of the script.

bun run --filter ./packages/dep0 --elide-lines=5 script

Thanks to @martinamps for implementing this!

pnpx and pnpm dlx auto-replacement

When you use bun run to run package.json scripts, we swap out yarn run, npm run, and pnpm run with bun run (so that you don't have to update your package.json). Now we also replace pnpx and pnpm dlx with bunx.

Thanks to @pfgithub for implementing this!

bun install

New: slow postinstall script logging

Buggy postinstall scripts on npm can cause your install to hang.

To help identify the package causing the hang, bun install now logs the package name of any postinstall scripts that takes longer than 30 seconds to run.

If you run bun install with --verbose, it will log the package name every 2 seconds instead of every 30 seconds.

New: bundle(d)Dependencies in bun install

Bun now supports handling bundle(d)Dependencies in the bun install command. This update allows users to specify dependencies that should be bundled together, streamlining package management. This makes it easier for users to create consistent and manageable projects, especially when dealing with multiple dependencies.

Thanks to @dylan-conway for implementing this!

New: --lockfile-only option for bun install

You can now use the --lockfile-only option with the bun install command to generate a lockfile without installing any dependencies. This is useful when you want to update or create a lockfile based on your package.json without affecting the state of your node_modules directory.

Thanks to @dylan-conway for implementing this!

New: --peer option in bun add

You can now add a package as a peer dependency using the --peer option with the bun add command. This functionality allows users to specify peer dependencies directly from their terminal, enhancing the usability of package management in Bun.

bun add --peer @types/bun

Thanks to @riskymh for implementing this!

Fixed: bun.lock file permissions

Bun's text-based lockfile was being marked as world-writable and executable, which was unnecessary.

This has been fixed, thanks to @dylan-conway!

Fixed: install.cache.dir in bunfig.toml

The install.cache.dir option now correctly reads the cache directory from your bunfig.toml. This change ensures that the cache directory can be customized through your configuration.

[install]
cache.dir = "./your_custom_cache_directory"

Thanks to @chawyehsu for implementing this!

Fixed: crash in bun patch --commit

A crash in bun patch --commit has been fixed, thanks to @dylan-conway!

Fixed: quote-escaping in bun.lock package names

Invalid "name" fields in package.json files were being persisted in the bun.lock file without escaping the quote character. This has been fixed, thanks to @dylan-conway!

Node.js compatibility improvements

100% of node:path tests now pass

Bun's node:path module now passes 100% of Node.js' test suite.

Fixed: potential crash when joining long win32 paths in node:path

A crash that could occur when joining very long paths using the node:path module has been fixed, thanks to @DonIsaac!

98.08% of node:zlib tests now pass

All but 1 test in the node:zlib module now pass.

Thanks to @nektro for this!

Fixed: memory leak in node:zlib

A memory leak in the node:zlib module has been fixed. This particularly impacted Brotli compression.

Fixed: zlib dictionary option handling

The dictionary option in zlib now works as expected. This fixed an issue impacting some Minecraft servers using Bun.

const deflate = zlib.createDeflate({ dictionary: Buffer.from("...") });

Fixed: napi_set_last_error method in Node-API

The napi_set_last_error method in Node-API now sets an appropriate error code prior to returning. Previously, we were not consistently setting the error code prior to returning from Node-API functions - which caused some native addons to not receive the correct error code after a failed function call.

Thanks to @190n for implementing this!

Fixed: process.kill allows zero or negative PIDs

An incorrect validation was preventing process.kill from accepting negative PIDs. Negative PIDs let you kill an entire process group instead of just a single process.

const { spawn, exec } = require("child_process");

// Starting a process
const child = spawn("some-command");

// Killing the process using a negative PID
process.kill(-child.pid);

Thanks to @nektro for implementing this!

--expose-gc flag adds globalThis.gc

Bun now implements the Node.js --expose-gc CLI flag, alongside the Bun.gc() method. This CLI flag adds gc to the global scope, which allows you to manually trigger garbage collection in your code.

bun --expose-gc <script.js>

The Bun.gc() method is the same as globalThis.gc.

Fixed: process.memoryUsage().arrayBuffers

Previously, process.memoryUsage().arrayBuffers was always reporting 0. Now we do our best to report the correct number.

What's interesting about this is a difference in how JavaScriptCore and V8 handle ArrayBuffer memory. In JavaScriptCore, typed array's buffers are "owned" by the TypedArray type (a Uint8Array and not an ArrayBuffer, for example). They don't become "shared" between Uint8Array and ArrayBuffer (or other typed arrays) until either:

  • The .buffer property was accessed (creating the ArrayBuffer).
  • The .subarray method was called (creating a shared reference).
  • The typed array size is > 512 bytes.
  • The typed array was externally allocated outside of JavaScript (such as when reading a file in Bun)

When the ArrayBuffer's contents are not owned by JavaScriptCore, they aren't included in the process.memoryUsage().arrayBuffers statistic, and instead are accounted for in the process.memoryUsage().external statistic.

Improved: module.findSourceMap

require("module").findSourceMap no longer throws an error when called. Previously, this threw an error saying we don't implement module.SourceMap yet. It's still not implemented, but throwing an error caused issues with Next.js in certain cases - so now we return undefined instead, which matches Node.js' behavior when no source map is found.

bun build

Improved stack overflow detection

Bun's JavaScript parser now proactively checks if it's about to run out of stack space, and throws an artifical stack overflow error.

Fixed (now a recoverable error):

❯ bun lots-of-for-loop.js
286 | for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++)
                                                      ^
error: Maximum call stack size exceeded
    at lots-of-for-loop.js:286:49

Previously, bun would crash with a hard SIGILL, which is not recoverable.

bun: killed SIGILL (core dumped)

Fixed: large number of if statements led to stack overflow

Two things caused stack overflows with large numbers of nested if statements:

  1. The statement parsing step for if statements was always recursive, even if the if statement was not nested. This recursion used too much stack space. Now it's no longer recursive.

  2. The statement visiting step in Bun's JavaScript parser was originally a ~1,300 line function. This used several kilobytes of stack space, which became a problem when recursing through heavily nested if/for loops. We split it into more functions and avoided recursion more frequently.

Fixed: #bun pragma

Ordinarily, bun transpiles every file at runtime. To avoid potentially transpiling the same file multiple times, bun looks for a // @bun pragma comment.

There was a bug where Bun's JavaScript lexer would sometimes interpret the string #bun present in comments as the bun pragma meant for disabling transpilation.

This has been fixed, thanks to @DonIsaac!

Fixed: cross-compile for musl in bun build --compile

The code that parses the --target flag in bun build --compile wasn't updated for when we added musl support in Bun v1.1.40. This has been fixed, thanks to @thecrypticace.

Fixed: sourcemap comments CSS files

The //# sourceMappingURL comment meant for JavaScript files was being printed in CSS files. This has been fixed.

Fixed: crash when parsing malformed TypeScript enums

A crash that occurred when handling malformed enums in the transpiler has been fixed.

// Example of a malformed enum that will generate a parse error
enum Foo { [2]: 'hi' }

Thanks to @paperclover for implementing this!

Runtime improvements

Override "Content-Length" and Transfer-Encoding in Bun.serve()

Bun.serve now supports overriding the "Content-Length" and Transfer-Encoding headers.

This is particularly useful for HEAD requests, where you want to return a Content-Length but not actually send the body.

const app = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello World", {
      headers: { "Content-Length": "7" },
    });
  },
});

Thanks to @cirospaciari for implementing this!

New: stat method in Bun.file(path)

You can now use stat without node:fs in Bun.

import { file } from "bun";

await file("./path-to-file.txt").stat();

This returns useful information about the file, such as its size, type, and last modified date. This returns the same type as in node:fs's stat function.

New: delete method in Bun.file(path)

You can now delete files in Bun, without node:fs.

import { file } from "bun";

await file("./path-to-file.txt").delete();

More accurate heap snapshots

The memory cost of adding entries to performance.mark and performance.measure is now reported accurately in heap snapshots.

Fixes to stack traces with eval and node:vm

Ordinarily, Bun transpiles every file at runtime. This also means we have to sourcemap stack traces and error messages.

We don't transpile code for node:vm or for eval (it would break compatibility). However, in certain cases, when some of the stack frames in a stack trace were from eval or node:vm and some of them were from Bun, the trace could get incorrectly sourcemapped.

Also, the //# sourceURL comment was not always being respecteed in stack traces, which is particularly unhelpful for eval and node:vm where the sourceURL can be passed in (via comment for eval, and via options in node:vm).

This has been fixed.

Fixed: stack overflow in console.log

A stack overflow causing an unrecoverable crash that could occur in console.log has been fixed.

Now, console.log proactively checks for how much stack space remains, and throws a StackOverflowError if it's about to run out.

Along the way, we've also fixed several cases where Bun would use large amounts of stack space for file path buffers on Windows (Windows file paths go up to 64 KB)

Fixed: memory leak in WebSocket client

A memory leak impacting the WebSocket client has been fixed.

remove and isRemoved in HTMLRewriter's DocType

Bun's HTMLRewriter implementation now includes remove() and isRemoved() methods. The remove() method allows users to delete the <!DOCTYPE> declaration from the HTML document during transformation, and the isRemoved() property is a boolean that tells you if the <!DOCTYPE> has been removed.

const html = "<!DOCTYPE html><html><head></head><body>Hello</body></html>";

const rewriter = new HTMLRewriter().onDocument({
  doctype(doctype) {
    doctype.remove();
    console.log(doctype.removed); // Logs: true
  },
});

const result = rewriter.transform(html);
console.log(result); // Logs: "<html><head></head><body>Hello</body></html>"

Thanks to @kimberly for implementing this!

Fixed: formatting Set in Bun.inspect

The formatting of Set objects in the Bun.inspect() function has been corrected. This ensures that when a Set is passed to Bun.inspect(), it will now display its contents properly formatted.

import { it, expect } from "bun:test";

it("Set is propperly formatted in Bun.inspect()", () => {
  const set = new Set(["foo", "bar"]);
  const formatted = Bun.inspect({ set });
  expect(formatted).toBe(`{
  set: Set(2) {
    "foo",
    "bar",
  },
}`);
});

Thanks to @laesse for implementing this!

Fixed: socket close behavior

In rare cases, the close callback for a socket could be called after the end callback. This has been fixed.

Fixed: Theroetical crash when iterating over properties

A bug in our JavaScriptCore <-> Zig bindings could lead to a crash when native code iterated through properties of an object in rare cases. This has been fixed.

Fixed: export default <file-path> in error messages

A bug that could cause bun to display "export default '<file-path>'" as the source code for an error message has been fixed.

Bun.sql is almost ready

Over the last year, one of my side projects has been a fast, builtin PostgreSQL client for Bun, and it's almost ready.

import { sql } from "bun";
import { expect } from "bun:test";

const result = await sql`select 1 as x`;
expect(result).toEqual([{ x: 1 }]);

Bun.sql will be released in Bun v1.2 - coming in 13 days at the time of writing. You can try it in the canary build of Bun.

Here's what's new for Bun.sql in this release.

Fixed: duplicate column names & numeric column names

A crash that occurred when duplicate column names were returned in query results has been fixed.

const result = await sql`select 1 as x, 2 as x, 3 as x`;
expect(result).toEqual([{ x: 3 }]);

const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 0 as "0"`;
expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, "0": 0 }]);

Previously, we also wouldn't properly support numeric column names.

Fixed: JSONB support

Bun's builtin PostgreSQL client now supports the jsonb data type.

import { sql } from "bun";
import { expect } from "bun:test";

test("jsonb support", async () => {
  const result = await sql`select '{"foo": "bar"}'::jsonb as x`;
  expect(result).toEqual([{ x: { foo: "bar" } }]);
});

onopen and onclose callbacks

The onopen and onclose tell you when the database connection opened or closed.

import { SQL } from "bun";

const sql = new SQL({
  url: "postgres://localhost:5432/mydb",
  onopen: () => console.log("Connection opened"),
  onclose: () => console.log("Connection closed"),
});

const [{ x }] = await sql`select 1 as x`;
console.log({ x });
await sql.close();
console.log("closed");

This logs:

Connection opened
{ x: 1 }
Connection closed
closed

Thanks to @dylan-conway for this addition!

Error codes for PostgreSQL errors

The following error codes are now available for PostgreSQL errors.

  • "ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2"
  • "ERR_POSTGRES_CONNECTION_CLOSED"
  • "ERR_POSTGRES_CONNECTION_TIMEOUT"
  • "ERR_POSTGRES_EXPECTED_REQUEST"
  • "ERR_POSTGRES_EXPECTED_STATEMENT"
  • "ERR_POSTGRES_IDLE_TIMEOUT"
  • "ERR_POSTGRES_INVALID_BACKEND_KEY_DATA"
  • "ERR_POSTGRES_INVALID_BINARY_DATA"
  • "ERR_POSTGRES_INVALID_BYTE_SEQUENCE"
  • "ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING"
  • "ERR_POSTGRES_INVALID_CHARACTER"
  • "ERR_POSTGRES_INVALID_MESSAGE"
  • "ERR_POSTGRES_INVALID_MESSAGE_LENGTH"
  • "ERR_POSTGRES_INVALID_QUERY_BINDING"
  • "ERR_POSTGRES_INVALID_SERVER_KEY"
  • "ERR_POSTGRES_INVALID_SERVER_SIGNATURE"
  • "ERR_POSTGRES_LIFETIME_TIMEOUT"
  • "ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET"
  • "ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET"
  • "ERR_POSTGRES_OVERFLOW"
  • "ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64"
  • "ERR_POSTGRES_SASL_SIGNATURE_MISMATCH"
  • "ERR_POSTGRES_SERVER_ERROR"
  • "ERR_POSTGRES_SYNTAX_ERROR"
  • "ERR_POSTGRES_TLS_NOT_AVAILABLE"
  • "ERR_POSTGRES_TLS_UPGRADE_FAILED"
  • "ERR_POSTGRES_UNEXPECTED_MESSAGE"
  • "ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD"
  • "ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD"
  • "ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT"
  • "ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE"

Other changes

  • The list of completions used for bun add is now compressed, which saves about 100 KB.
  • We rewrote the code that checks for unicode identifiers which shaved off about 250 KB.

Thanks to 22 contributors!