Blog

Bun v1.0.16

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

curl -fsSL https://bun.sh/install | bash

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 @paperclover 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: