Bun

Bun v1.1.13


Jarred Sumner · June 5, 2024

Bun v1.1.13 is here! This release fixes 97 bugs (addressing 211 👍). Reliability improvements to bun install, WebSocket server, fetch, worker_threads. worker_threads now supports eval. URL.createObjectURL, stack trace error location improvements, several bun install fixes, and many crashes and bugs fixed.

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

Previous releases

  • v1.1.11 fixes 36 bugs (addressing 362 👍). bun update saves dependency updates to package.json. A peer dependencies resolution bug in bun install is fixed. setTimeout gets 5x faster throughput on Linux. Node.js compatibility improvements to fs, zlib, timers, http, dgram and module. Event loop improvements on macOS. Windows reliability improvements.
  • v1.1.10 fixes 20 bugs. 2x faster uncached bun install on Windows. fetch() uses up to 2.8x less memory. Several bugfixes to bun install, sourcemaps, Windows reliability improvements and Node.js compatibility improvements
  • v1.1.9 fixes 67 bugs (addressing 150 👍). Fixes to: workspaces in bun install, sourcemaps in bun build, IPv6 & VPN connectivity, Loading UNC paths, junctions, symlinks, and pnpm on Windows, fetch() gets faster, added dns.prefetch() API, atob() gets 8x faster, toString('base64url') gets 5x faster, expect().toBeReturned() matcher, Node.js compatibility improvements, Bun Shell fixes, and lots more bugfixes.
  • v1.1.0 Bundows. Windows support is here!

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

node:worker_threads now supports eval: true

Similar to the web standards, node:worker_threads has a feature to allow taking a string of source code.

node-worker-eval.ts
import * as wt from 'node:worker_threads';
const code = `
  import { parentPort } from 'node:worker_threads';
  const foo: number = 123;
  parentPort.postMessage({ foo } satisfies Data);
`;
const worker = new wt.Worker(code, { eval: true });
worker.on('message', data => {
  console.log(data);
});
bun node-worker-eval.ts
{
  foo: 123,
}

Implemented URL.createObjectURL

Bun now supports URL.createObjectURL, which creates a URL from a Blob object. These urls can then be used in APIs like fetch, Worker, and import.

When combined with Worker, it allows for an easy way to spawn additional threads without creating a new separate URL for the worker's script. Since worker scripts also run through Bun's transpiler, TypeScript syntax is supported.

worker-object-url.ts
const code = `
  const foo: number = 123;
  postMessage({ foo } satisfies Data);
`;
const blob = new File([code], "hey.ts");
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
worker.onmessage = ({data}) => {
  console.log("data:", data);
};
bun worker-object-url.ts
{
  foo: 123,
}

expect supports custom failure messages

Bun now supports custom failure messages by passing a string as the second argument to expect. This works the same as it does in Vitest, allowing you to document what the assertion is checking.

custom-message.test.ts
import { test, expect } from 'bun:test';

test("sample", () => {
  expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
});
 $ bun test custom-message
 bun test v1.1.12 (43f0913c)

 test.test.ts:
 1 | import { test, expect } from 'bun:test';
 2 |
 3 | test("sample", () => {
 4 |   expect(0.1 + 0.2, "Floating point has precision error").toBe(0.3);
                                                               ^
-error: expect(received).toBe(expected)
+error: Floating point has precision error

 Expected: 0.3
 Received: 0.30000000000000004

 at /private/tmp/scratchpad_20240606T054447/test.test.ts:4:3
 ✗ sample [1.94ms]

This is most useful when the value evaluates to a boolean. Instead of just seeing "Expected true, recieved false", tests can make it immediatly obvious what the error is.

Bun install reliability

This release includes several reliability improvements to bun install.

Fixed: Crash when certain peer dependencies install

When certain peer dependencies are installed and not present in the cache, Bun would potentially crash. This crash has been reported 167 times.

Thanks to @dylan-conway for fixing this!

Fixed: Manifest cache string deduplicating bugfix

When a dependency name and a dependency version existed in the same npm registry manifest file, Bun would potentially deduplicate the strings incorrectly, leading Bun to attempt to download the wrong package. This bug is pretty rare - you would have to have a dependency name that is also a version number, or the version number would have be the same as a dependency name (like an npm tag named "react" and a dependency named "react" in the same registry manifest api response).

Thanks to @dylan-conway for fixing this!

Fixed: Semver parsing edge case with prerelease tags

A bug in Bun's semver parser has been fixed, thanks to @dylan-conway. This bug would cause invalid semver to sometimes successfully install versions with prerelease tags if . immediately followed the patch number. For example, 1.0.0.alpha would parse as 1.0.0-alpha and install if that version existed.

Fixed: Crash and assertion failure when printing bun.lockb in yarn.lock format

A crash when running bun bun.lockb has been resolved thanks to @dylan-conway. This the assertion failure would sometimes occur when packages had the same dependency in "dependencies" and "optionalDependencies" in their package.json.

Fixed: Npm package manifest validation bugfix

Bun install uses npm manifest files to retrieve package information and determine what dependencies need to be installed. When receiving these manifests, Bun will make sure the "name" property matches the package name from the request, and error if it does not. This fix allows scoped packages to be url encoded, where possibly @ or / could be replaced with %40 or %2F.

{
  "name": "%40foo%2fbar"
  // ...
}

Thanks to @dylan-conway for fixing this!

Fixed: Package manifest cache bugfix

A bug where a re-install of node_modules could fail when switching between projects using different default or scoped registries has been fixed.

Thanks to @dylan-conway for fixing this!

Fixed: Installing git dependencies with missing package.json files or "name" fields

Bun will now successfully install git dependencies that do not have a package.json file committed, or have a package.json file without a "name" field. The dependency name will be the repository name in these situations.

Thanks to @dylan-conway for fixing this!

Fixed: Aliased workspace dependencies fix

A bug preventing aliased workspace dependencies without a version in their package.json has been fixed. For example, this workspace dependency will now work as expected:

// ./packages/foo/package.json
{
  "name": "foo",
  "version": "1.0.0",
  "dependencies": {
    "is-odd": "workspace:is-even@latest"
  }
}

// ./packages/is-even/package.json
{
  "name": "is-even",
}

Thanks to @dylan-conway

Incorrect manifest cache invalidation optimization

We've disabled an optimization in bun install that would cause bun install to potentially hang or receive "Connection closed" errors.

npm's registry api returns a Last-Modified header, but ignores If-Not-Modified-Since header. When the Last-Modified header matched the stored value, Bun would ignore the remaining response body and pretend a 304 was returned. This skips donwloading, decompressing, and parsing the manifest JSON unnecessarily. However, the implementation of this optimization needs to be more robust to handle HTTP keep-alive correctly. For now, we've disabled this optimization.

5 new bun:test matchers

This release adds 5 new matchers to bun:test:

  • toContainAllKeys
  • toContainValue
  • toContainValues
  • toContainAllValues
  • toContainAnyValues

These matchers are useful for testing if objects contain or have specific keys or values.

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

test("hello", () => {
  expect({ a: "hello", b: "world" }).toContainAllKeys(["a", "b"]);
  expect({ a: "hello", b: "world" }).toContainValue("hello");
  expect({ a: "hello", b: "world" }).toContainValues(["hello", "world"]);
  expect({ a: "hello", b: "world" }).toContainAllValues(["hello", "world"]);
  expect({ a: "hello", b: "world" }).toContainAnyValues(["world"]);
});

Thanks to @nithinkjoy_tech for adding these matchers!

fetch stability improvement

We fixed a bug where if you sent thousands of concurrent HTTP requests in a short time frame, some would fail with The socket connection was closed unexpectedly.

The cause of this was due to Bun's handling of TCP FIN packets. After receiving a TCP FIN packet, Bun now sets SO_LINGER on the socket to 0, causing it to immediately close instead of risking getting stuck in the TIME_WAIT state and then running out of socket descriptors.

Fix stack trace line/column numbers being offset

The code bun uses to remap errors to their original line and column number had some very subtle edge cases. The shortest reproduction was from strictEqual.

import { strictEqual } from "node:assert";

(function () {});

strictEqual(1, 2);

Which would point to the wrong position.

1 | import { strictEqual } from 'node:assert';
2 |
3 | (function () { })
     ^
AssertionError: Expected values to be strictly equal:
    at module code (/tmp/scratchpad/index.ts:3:2)

Thanks to @paperdave, this entire class of bug has been fixed by fixing a mistake in how line and column numbers are read from JavaScriptCore. The error divot, the printed stack trace, and error.stack all point to the correct source location.

3 | (function () { })
4 |
5 | strictEqual(1, 2);
    ^
AssertionError: Expected values to be strictly equal:
    at module code (/tmp/scratchpad/index.ts:5:1)

This is important because when using bun build --target=bun --sourcemap --minify, all source code gets placed on one line, which can cause errors to point to positions in the file which don't even exist.

Minify Infinity

When bundling with --minify-syntax, the Infinity constant will be shortened into 1/0

infinity.ts
console.log(Infinity);
bun build infinity.ts --minify
console.log(1/0);

WebSocket server reliability improvements

This release fixes three rare bugs in Bun's WebSocket server:

  1. A crash could occur when the socket connection hung up unexpectedly in a specific code path. We did not see reports of this, but did notice it in debug builds of Bun.
  2. A case of undefined behavior in the WebSocket server when sending a large message without compression, without TLS, and with no backpressure has been fixed. We have not seen any reports of this causing a crash or data corruption.
  3. A crash when passing invalid argument types to isSubscribed, subscribe, or unsubscribe has been fixed. This could also happen if the machine ran out of memory. This issue was reported 1 time.

Fixed: Async WebSocket Server upgrade with custom subprotocols

A bug where the following code would close the connection immediately has been fixed:

import type { ServerWebSocket } from "bun";

const delay = (ms: number) => new Promise((ok) => setTimeout(ok, ms));

Bun.serve<{}>({
  fetch: async (req, server) => {
    await delay(1);

    if (server.upgrade(req)) return;
  },
  websocket: {
    open(ws: ServerWebSocket<{}>): void | Promise<void> {
      console.log("open");
    },
    close(
      ws: ServerWebSocket<{}>,
      code: number,
      reason: string,
    ): void | Promise<void> {
      console.log("closed", code);
    },
  },
});

new WebSocket("http://localhost:3000", "ocpp1.6");

Thanks to @cirospaciari for fixing this!

Fixed: UTF16 Content-Type header in WebSocket server

If the JavaScript string for "Content-Type" was encoded as UTF-16, Bun would not read it correctly. This has been fixed, thanks to @cirospaciari.

Fixed: WebSocket memory leak on close

A memory leak when closing WebSockets has been fixed, thanks to @cirospaciari!

Fixed: AbortSignal.any() bugfix

A bug causing signals from AbortSignal.any() to not receive an abort event for each dependent signal has been fixed, thanks to @cirospaciari!

The following code will now work as expected:

const controller = new AbortController();

const signal = AbortSignal.any([controller.signal]);

signal.addEventListener("abort", () => {
  console.log("aborted!");
});

controller.abort();

Faster crypto.pbkdf2

We've reimplemented crypto.pkdf2 to use optimized BoringSSL functions instead of a JavaScript polyfill implementation.

New:

cpu: Apple M3 Max
runtime: bun 1.1.13 (arm64-darwin)

benchmark                                         time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------------------ -----------------------------
pbkdf2(iterations = 1000, 'sha256') -> 32       81.5 µs/iter  (69.67 µs … 823.54 µs)   81.5 µs  100.5 µs 110.08 µs
pbkdf2(iterations = 500_000, 'sha256') -> 32   31.74 ms/iter   (31.16 ms … 33.28 ms)  31.83 ms  33.28 ms  33.28 ms

Previously:

cpu: Apple M3 Max
runtime: bun 1.1.10 (arm64-darwin)

benchmark                                         time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------------------ -----------------------------
pbkdf2(iterations = 1000, 'sha256') -> 32       2.37 ms/iter     (2.05 ms … 5.15 ms)   2.26 ms   5.06 ms   5.06 ms
pbkdf2(iterations = 500_000, 'sha256') -> 32     1.24 s/iter       (1.22 s … 1.26 s)    1.25 s    1.26 s    1.26 s

Node.js, for comparison:

cpu: Apple M3 Max
runtime: node v22.2.0 (arm64-darwin)

benchmark                                         time (avg)             (min … max)       p75       p99      p995
------------------------------------------------------------------------------------ -----------------------------
pbkdf2(iterations = 1000, 'sha256') -> 32     213.68 µs/iter   (195.08 µs … 2.35 ms) 217.33 µs 274.71 µs 289.21 µs
pbkdf2(iterations = 500_000, 'sha256') -> 32   104.6 ms/iter  (97.97 ms … 114.64 ms) 110.37 ms 114.64 ms 114.64 ms

Fixed: structuredClone(File)

structuredClone now supports File objects, which are used in postMessage.

structured-clone.ts
const blob = new File(["hello"], "hello.txt");
const cloned = structuredClone(blob);
console.log(cloned.name === "hello.txt"); // true
// Previously: false

Thanks to @dylan-conway for fixing this!

Fixed: Worker .terminate() stability improvements

A crash has been fixed with terminating Workers. This was caused by a race condition with pending work (such as fetch, async node:fs, and Bun.serve) while the termination was in progress.

Fixed: node:https client bug with custom checkServerIdentity

When using node:https or tls:connect with a custom checkServerIdentity function, a race condition could potentially cause the function to never be called. This bug has been fixed.

This does not impact the default checkServerIdentity function, as that takes a completely separate code path.

Fixed: Crashes during bun update with unresolved packages

A crash when updating peer dependencies has been resolved where during an update a invalid_package_id was seen, indicating the package was not yet resolved. Previously, we assumed at this stage that packages were always resolved, but an edge case with peer dependencies can leave this unresolved.

Thanks to @dylan-conway

Fixed: Windows command line parsing crash

A crash relating to invalid utf16 in windows command line strings has been resolved.

Fixed: bun update preserves ~ version specifiers

If a package.json specified a dependency version such as ~1.1.0, the ~ will be preserved after the update. Previously, this was only done for ^ ranges.

package.json
{
  "dependencies": {
    // Before running `bun update`
    "elysia": "~1.0.21",

    // After `bun update`
    "elysia": "~1.0.21",
    "elysia": "~1.0.22",
  }
}

Thanks to @dylan-conway

Fixed: Bun Shell fixes

A couple bugs involving invalid UTF-16 and Bun.$.escape have been fixed, along with a panic when a command name expands to an empty string.

These are fixed to @zackradisic

Fixed: Bun.deepEquals with undefined values edge case

Comparing two objects with Bun.deepEquals would sometimes incorrectly return true if one of the objects had undefined properties and the other had the same number of properties.

console.log(Bun.deepEquals({ a: undefined }, { b: 1 })); // true

This is fixed thanks to @dylan-conway

Fixed: bun create typo fix

bun create using a local template will suggest to run cd with a relative path to the created project. When the destination was the current directory, it would suggest running cd with no arguments.

$ bun create elysia
Created elysia project successfully

# To get started, run:

- cd
  bun run src/index.ts

Thanks to @Sushants-Git

Fixed: bun create with a local folder template on Windows

Previously, creating a project using a local template on Windows did not work. This has been resolved.

Thanks to @dylan-conway for fixing this.

Fixed: Help menu fixes

Bun's help menu includes example packages to use with bun create, bun add. At some point, the random lists used for these got swapped.

Bun is a fast JavaScript runtime, package manager, bundler, and test runner. (1.1.13)

Usage: bun <command> [...flags] [...args]

Commands:
  run       ./my-script.ts       Execute a file with Bun
            lint                 Run a package.json script
  test                           Run unit tests with Bun
  x         nuxi                 Execute a package binary (CLI), installing if needed (bunx)
  repl                           Start a REPL session with Bun
  exec                           Run a shell script directly with Bun

  install                        Install dependencies for a package.json (bun i)
- add       next-app             Add a dependency to package.json (bun a)
+ add       zod                  Add a dependency to package.json (bun a)
  remove    backbone             Remove a dependency from package.json (bun rm)
  update    elysia               Update outdated dependencies
  link      [<package>]          Register or link a local npm package
  unlink                         Unregister a local npm package
  pm <subcommand>                Additional package management utilities

  build     ./a.ts ./b.jsx       Bundle TypeScript & JavaScript into a single file

  init                           Start an empty Bun project from a blank template
- create    next-app             Create a new project from a template (bun c)
+ create    zod                  Create a new project from a template (bun c)
  upgrade                        Upgrade to latest version of Bun.
  <command> --help               Print help text for command.

Learn more about Bun:            https://bun.sh/docs
Join our Discord community:      https://bun.sh/discord

Thanks to @MARCROCK22 for fixing this

Improved error message for Bun.serve when passed a non-Response object

When passing a non-Response object to Bun.serve, Bun will now throw an error with a more helpful message.

// Before:
error: Expected a Response object

// After:
error: Expected a Response object, but received '{}'

Fixed: node:crypto randomInt callback

node:crypto's randomInt function now correctly accepts a callback with the random number.

import crypto from "crypto";

crypto.randomInt(0, 5000, (err, val) => {
  console.log(val);
});

This is fixed thanks to @nektro

Fixed: Classes won't emit unnecessary constructor functions

When bundling TypeScript, Bun will no longer emit an empty constructor for extended classes. This reduces bundle sizes, and speeds up class construction, as this constructor would hit a slow path in JavaScriptCore.

class A {
  constructor(argument) {
    console.log(`Hello ${argument}`);
  }
}

class B extends A {}

new B("World");
 // index.ts
 class A {
   constructor(argument) {
     console.log(`Hello ${argument}`);
   }
 }

 class B extends A {
-  constructor() {
-    super(...arguments);
- }
 }
 new B("World");

Thanks to @paperdave

Fixed: Crash involving setTimeout and process.on('uncaughtException')

A crash when throwing an exception in a setTimeout and using it in process.on('uncaughtException') has been fixed thanks to @paperdave.

Libarchive, mimalloc, and zstd were updated

libarchive was updated to version 3.7.4, mimalloc was updated to version 2.1.8, and zstd was updated to version 1.5.6.

Thanks to 11 contributors!