Bun

Bun v1.0.16


Jarred Sumner · December 10, 2023

Bun v1.0.16 fixes 49 bugs (addressing 38 👍 reactions). Rewritten IO for Bun.file, Bun.write auto-creates the parent directory if it doesn't exist, expect.extend inside of preload works, napi_create_object gets 2.5x faster, bugfix for module resolution impacting Astro v4 and p-limit, console.log bugfixes, and more.

Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager — all in one. In case you missed it, here are some of the recent changes to Bun:

  • v1.0.12 - Adds bun -e for evaluating scripts, bun --env-file for loading environment variables, server.url, import.meta.env, expect.unreachable(), improved CLI help output, and more
  • v1.0.13 - Fixes 6 bugs (addressing 317 👍 reactions). 'http2' module & gRPC.js work now. Vite 5 & Rollup 4 work. Implements process.report.getReport(), improves support for ES5 'with' statements, fixes a regression in bun install, fixes a crash when printing exceptions, fixes a Bun.spawn bug, and fixes a peer dependencies bug
  • v1.0.14 - Bun.Glob, a fast API for matching files and strings using glob patterns. It also fixes a race condition when extracting dependencies during bun install, improves TypeScript module resolution in node_modules, and makes error messages easier to read.
  • v1.0.15 - Fixes 23 bugs (addressing 117 👍 reactions), tsc starts 2x faster. Stable WebSocket client, syntax-highlighted errors, cleaner stack traces, add custom test matchers with expect.extend() + additional expect matchers.

To install Bun:

curl
npm
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g 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

Bun.write now creates the parent directory if it doesn't exist

Previously, Bun.write would throw an error if the directory didn't exist. Now, it will create the directory if it doesn't exist.

// script.ts
import { write } from "bun";
await write("i/dont/exist.txt", "Hello, world!");

Now, i/dont will be created if it doesn't exist.

$ bun script.ts

Previously, it would throw an error:

1 | import { write } from "bun";
2 | await write("i/dont/exist.txt", "heyyy");
          ^
ENOENT: No such file or directory
   path: "i/dont/exist.txt"
 syscall: "open"
   errno: -2

To throw an error when the parent directory doesn't exist: createPath: false

This behavior can be disabled by passing createPath: false to Bun.write.

// script.ts
import { write } from "bun";
await write("i/dont/exist.txt", "Hello, world!", { createPath: false });

Now, it will throw an error if the directory doesn't exist.

$ bun script.ts
1 | import { write } from "bun";
2 | await write("i/dont/exist.txt", "heyyy", { createPath: false });
          ^
ENOENT: No such file or directory
   path: "i/dont/exist.txt"
 syscall: "open"
   errno: -2

Rewritten IO for Bun.file

The internals of Bun.file on Linux have been rewritten to use epoll instead of io_uring, which improves compatibility in Docker and cloud hosts like Vercel and Google Cloud Run.

This addresses 11 issues related to EBADF when using Bun.file:

The impetus for this change was Docker disabled io_uring by default in November 2023.

Bun.write() and Bun.file().text() gets 3x faster under concurrent load

Along the way, we improved the performance of reading and writing files concurrently using Bun.file() and Bun.write().

Reading:

❯ N=100 hyperfine "bun ./read.js" "bun-1.0.15 read.js" --warmup=20
Benchmark 1: bun ./read.js
  Time (mean ± σ):      40.9 ms ±   2.9 ms    [User: 27.6 ms, System: 181.7 ms]
  Range (min … max):    37.1 ms …  48.3 ms    73 runs

Benchmark 2: bun-1.0.15 read.js
  Time (mean ± σ):     134.5 ms ±  19.0 ms    [User: 24.8 ms, System: 82.5 ms]
  Range (min … max):    98.8 ms … 161.2 ms    23 runs

Summary
  bun ./read.js ran
    3.29 ± 0.52 times faster than bun-1.0.15 read.js

Writing:

❯ N=100 hyperfine "bun ./write.js" "bun-1.0.15 write.js" --warmup=20
Benchmark 1: bun ./write.js
  Time (mean ± σ):      35.0 ms ±   8.0 ms    [User: 13.0 ms, System: 364.6 ms]
  Range (min … max):    25.7 ms …  71.5 ms    83 runs

Benchmark 2: bun-1.0.15 write.js
  Time (mean ± σ):     134.7 ms ±  13.6 ms    [User: 23.6 ms, System: 193.3 ms]
  Range (min … max):   117.8 ms … 171.2 ms    23 runs

Summary
  bun ./write.js ran
    3.85 ± 0.96 times faster than bun-1.0.15 write.js

read.js:

const promises = new Array(parseInt(process.env.N || "20"));
for (let i = 0; i < promises.length; i++) {
  promises[i] = Bun.file("out.log." + i).text();
}

await Promise.all(promises);

write.js:

const toPipe = new Blob(["abc".repeat(1_000_000)]);

const promises = new Array(parseInt(process.env.N || "20"));
for (let i = 0; i < promises.length; i++) {
  promises[i] = Bun.write("out.log." + i, toPipe);
}

await Promise.all(promises);

Fixed: Bun.stdin.text() incomplete reads

Previously, Bun.stdin.text() would sometimes not return all of the data from stdin.

import { stdin } from "bun";
console.log(await stdin.text());
$ echo "Hello, world!" | bun script.ts

Previously, sometimes it would print nothing:


Now, it prints the full input:

Hello, world!

napi_create_object gets 2.5x faster

expect and expect.extend in --preload

Previously, expect was scoped to the indivdual test file, which would break expect.extend if it was called in a shared file. This is now fixed, and you can now use a shared file containing your custom matchers such as.

custom-matchers.ts
import { expect } from "bun:test";

// Based off of from Jest's example documentation
// https://jestjs.io/docs/expect#expectextendmatchers
function toBeWithinRange(actual, floor, ceiling) {
  const pass = actual >= floor && actual <= ceiling;

  return {
    pass,
    message: () =>
      "expected " +
      this.utils.printReceived(actual) +
      " to " +
      (pass ? "not " : "") +
      "be within range " +
      this.utils.printExpected(`${floor} - ${ceiling}`),
  };
}

expect.extend({
  toBeWithinRange,
});

Then configure bunfig.toml to load them during testing,

bunfig.toml
test.preload = "./custom-matchers.ts"

and run bun test:

example.test.ts
import { expect, test } from "bun:test";

test("example", () => {
  expect(1234).toBeWithinRange(1000, 2000);
});
example.test.ts:
-TypeError: expect.extend is not a function. (In 'expect.extend({
-  toBeWithinRange
-})', 'expect.extend' is undefined)
-      at /custom-matchers.ts:21:1
+✓ example [1.31ms]

You can also use expect() outside of test callbacks.

-error: This function can only be used in a test.
-    at /custom-matchers.ts:21:1

Fixed: module resolution bug with "imports" in package.json

Packages can declare a list of internal-only imports in their package.json file, which Bun will use to resolve imports. This is useful for avoiding exposing internal files to users.

{
  "name": "my-package",
  "imports": {
    "my-package": "./dist/index.js"
  }
}

These imports are prefixed with #.

import { foo } from "#my-package";

This can also be used to rewrite imports from other packages. For example, if you wanted to rewrite all imports from async-hooks to #async-hooks, you could do:

{
  "name": "my-package",
  "imports": {
    "async-hooks": "async-hooks"
  }
}

Then, it will load the async-hooks package:

import { foo } from "#async-hooks";

Except, there was a bug that caused aliasing packages this way to not work in Bun. This has been fixed.

Notable packages this bug impacted:

  • Astro v4
  • p-limit

Fixed: holey arrays appearing as undefined in console.log

Previously, holey arrays would appear as undefined in console.log. This has been fixed.

console.log([1, , 3]);

Now:

[ 1, empty item, 3 ]

Previously:

[1, undefined, 3];

Fixed: file: URLs in createRequire() that contain spaces

Previously, createRequire() would throw an error when passed a file: URL which contained an unescaped space. This has been fixed.

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
require("file:///Users/jarred/My%20Folder/script.js");

Thanks to @paperdave for fixing this.

Fixed: console.log was omiting the length property sometimes

Sometimes, console.log would omit the length property of an object. This has been fixed.

console.log({ length: 1, a: 1 });

Previously, it would print:

{
  a: 1
}

Now, it prints:

{
  length: 1,
  a: 1,
}

Fixed: transpiler inconsistentcy with jsx key after spread

The following input would behave differently in Bun versus in TypeScript:

const obj = {};
console.log(<div {...obj} key="after" />, <div key="before" {...obj} />);

This has been fixed, thanks to @rhyzx.

Fixed: console.log("%d", value) bug

A bug causing %d to not handle non-numeric values correctly has been fixed.

console.log("%d", "1");

Previously, it would print:

0

Now, it prints:

1

Fixed: Syntax highlighter template string handling

In the previous version of Bun, we added syntax-highlighting to the code shown in error messages, but it has a bug that caused some template strings to crash the highlighter. This has been fixed.

Fixed: jest.fn(jest.fn()) crash

A crash when calling jest.fn(jest.fn(() => {})) has been fixed, thanks to @Hanassagi.

Thanks to 16 contributors!

We're always excited to welcome new contributors to Bun. 6 new contributors made their first contribution in this release.

Thanks to the 16 contributors who made this release possible: