Bun

Bun v1.0.19


Jarred Sumner · December 22, 2023

Bun v1.0.19 fixes 26 bugs (addressing 92 👍 reactions). Use @types/bun instead of bun-types. Fixes "lockfile had changes, but is frozen" bug. bcrypt & argon2 packages now work. setTimeout & setInterval get 4x higher throughput. module mocks in bun:test resolve specifiers. Optimized spawnSync() for large stdio on Linux. Bun.peek() gets 90x faster, expect(map1).toEqual(map2) gets 100x faster. Bugfixes to NAPI, bun install, and Node.js compatibility improvements

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.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.
  • v1.0.16 - Fixes 49 bugs (addressing 38 👍 reactions). Concurrent IO for Bun.file & Bun.write gets 3x faster and now supports Google Cloud Run & Vercel, 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
  • v1.0.17 - Fixes 15 bugs (addressing 152 👍 reactions). bun install postinstall scripts run for top 500 packages, bunx supabase starts 30x faster than npx supabase, bunx esbuild starts 50x faster than npx esbuild and bugfixes to bun install
  • v1.0.18 - Fixes 27 bugs (addressing 28 👍 reactions). A hang impacting create-vite & create-next & stdin has been fixed. Lifecycle scripts reporting "node" or "node-gyp" not found has been fixed. expect().rejects works like Jest now, and more bug fixes

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

Re-introducing @types/bun (formerly bun-types)

@types/bun gives you type definitions for Bun. It's a separate package from Bun itself, to make it easier to use Bun with your text editor or IDE.

Previously, this package was called bun-types and there were a lot of issues with it:

  • You had to set types in your tsconfig.json, which broke loading packages using the @types folder convention
  • We embedded a slightly modified version of @types/node, which frequently conflicted with the real @types/node package
  • It didn't "just work" with TypeScript unless you configured it correctly
  • DOM/Web types would occasionally conflict with Bun's types

Now, @types/bun should just work. It re-exports the real @types/node package so there's no conflicts. Since it's now in the @types namespace, it should work without breaking @types/* packages.

To install @types/bun:

bun add -d @types/bun

Or you can ue bun init:

bun init

bcrypt & argon2 packages now work

N-API compatibility improvements unlocked bcrypt and argon2 package support in Bun. These packages are widely used to securely hash & verify passwords.

Bun.password also lets you hash and verify passwords using either bcrypt or argon2 without any dependencies to install.

Fixed: "lockfile had changes, but is frozen" bug

The code path used by --frozen-lockfile to detect whether a lockfile changed sometimes incorrectly reported that the lockfile changed when it didn't. This would cause errors like:

error: lockfile had changes, but is frozen

To fix this, we've changed how we define "frozen". Instead of relying on input from the filesystem which can potentially change for unrelated reasons, we've switched it to using a hash of the package names and their versions sorted alphabetically with their version numbers. We think this is a more reliable approach.

To see what the hash of your lockfile is, run:

bun pm hash-string

View example output

setTimeout & setInterval get 4x higher throughput

Bun's setTimeout and setInterval implementations are now 4x faster on Linux x64. We've added a timer heap to more efficiently manage timers.

❯ bun setTimeout-leak-test.js
Executed 1003520 timers in 421.560553 ms

❯ bun-1.0.18 setTimeout-leak-test.js
Executed 1003520 timers in 2287.405973 ms

Thanks to @mitchellh's libxev for the timer heap implementation.

Why is timer performance important?

You might be thinking something like:

"Timers delay code execution. Why does it matter if it's fast?"

Timer scheduling has a significant performance impact on your code, and many libraries use timers to schedule work with a slight delay. Timers don't have to trigger much faster, but timer scheduling needs to be fast.

Bun's previous timer implementation was somewhat irresponsible. On Linux, it created a timerfd for every timer. This means every timer involves multiple system-calls and occupies a file descriptor. That's a lot of overhead for code which potentially runs very frequently.

Bun's new timer implementation uses a timer heap. This is a data structure that allows us to efficiently schedule potentially millions of timers.

Fixed: setInterval sometimes still ran after clearInterval

There was a bug where an already-cancelled call to setInterval would occasionally still run one time. This was fixed along the way to rewriting the timer implementation.

Better error for using await outside an async function

We've added a better error message when you use await outside of an async function.

❯ bun file.js # after
3 |   await fetch("https://example.com")
            ^
error: "await" can only be used inside an "async" function
    at file.js:3:9

2 | function ohNo() {
    ^
note: Consider adding the "async" keyword here
   at file.js:2:1

Before, it would just say:

❯ bun-1.0.18 file.js # before
3 |   await fetch("https://example.com")
            ^
error: Expected ";" but found "fetch"
    at file.js:3:9

Optimized Bun.spawnSync for large input on Linux

On Linux, Bun.spawnSync now gets smarter about how it reads and writes data from child processes. We've switched from a pipe which must be read & written in a loop by the parent process to an in-memory file descriptor which does not cause processes to block when reading or writing large amounts of data. This also lets us avoid cloning the data from the child process to the parent process which can be expensive for large amounts of data.

This change makes Bun.spawnSync 50% faster when the child process has a large amount of output.

Bun.spawnSync performance improvements

Bun.peek() gets 90x faster

Bun.peek(promise) lets you read a promise's value without waiting for it to resolve, if it's no longer pending. This is useful to avoid microtasks and improve performance.

We've switched the implementation from relying on C++ SPI to using a JavaScriptCore bytecode intrinsic which makes it 90x faster.

Thanks to @paperdave

expect(map1).toEqual(map2) gets 100x faster

We've optimized the implementation of expect(...).toEqual when comparing Map instances. This makes it 100x faster for maps. This also makes it faster for Set instances.

We've also fixed a bug where toEqual on large maps would sometimes incorrectly report the maps are not equal.

View on X

module mocks in bun:test resolve specifiers

Bun supports mocking modules in bun:test using the mock.module function. Previously, the specifier argument had to exactly match the resolved module specifier used when loading the overridden module.

This was very confusing! People usually don't think about the difference between a resolved module specifier and a module specifier, and that means it seemed to not work when it actually did. Since ES Modules resolve and link modules before evaluating them, the resolved module specifier is usually different from the module specifier you use in import statements.

Now, mock.module resolves the specifier before mocking the module. This means you can use the module specifier (the path you put in import statements) instead of the resolved module specifier (the path you see in error messages).

import { mock, test, expect } from "bun:test";
import _ from "lodash";

  // Before: you had to resolve it yourself:
  mock.module(require.resolve("lodash"), () => ({ default: "mocked" }));
  // Now: you can use the module specifier:
  mock.module("lodash", () => ({ default: "mocked" }));

test("lodash is mocked", () => {
  expect(_).toEqual("mocked");
});

Log slow postinstall scripts

If a post-install script takes longer than 500ms to run, bun install will now log it as a warning. This is useful to identify slow post-install scripts.

bun add v1.0.19 (7e59f287)

 installed re2@1.20.9

+warn: re2's install script took 57.7s

 92 packages installed [58.20s]

To avoid cluttering your terminal scrollback, we only log the slowest post-install script (not all of them).

Thanks to @paperdave.

Fixed: potential hang when running post-install scripts

There was an event loop bug that could cause bun install to potentially hang (by not realizing they've exited) when running post-install scripts. This was fixed, thanks to @dylan-conway.

bun install --verbose now streams post-install script output

When running bun install --verbose, post-install script output is now streamed to the terminal instead of buffered and printed at the end. This makes it easier to see what's happening. Thanks to @dylan-conway for implementing this.

Fixed: missing progress bar in post-install script output

In certain cases, bun install would not show a progress bar while the post-install scripts are running. This has been fixed, thanks to @dylan-conway.

Fixed: stringifying SHA1, SHA256, MD5 hashes sometimes output too long of a string

A bug when encoding the output of a SHA1, SHA256, or MD5 hash as a hex or base64 string would sometimes output a string that was longer than the contents of the hash. This is now fixed.

This impacted:

  • Bun.CryptoHasher
  • createHash in node:crypto

Upgraded SQLite from v3.38.5 to v3.44.2

bun:sqlite is a fast SQLite client built into Bun. On Linux, bun:sqlite is statically linked which means we embed the SQLite library into Bun itself. This means you don't need to install SQLite on your system to use it.

We've upgraded the embedded SQLite library from v3.38.5 to v3.44.2. You can see the full changelog here.

Fixed: TypeScript parser edgecase

Previously, the following input would incorrectly fail to parse:

const a = <T = any>(): T => null as T;
const b = a<string>

This is now fixed. Thanks to @paperdave for fixing this.

Fixed: crash in bun init

A crash that sometimes occurred in bun init has been fixed, thanks to @paperdave.

Fixed: regression with $NODE_ENV

In Bun v1.0.18 (the previous version), we accidentally removed setting a default value for NODE_ENV. This breaking change was not meant to be included in Bun v1.01.8. We may do a breaking change in the future to unset a default value (outside bun test), but we will not do this in a patch release.

Thanks to @paperdave for fixing this.

Fixed: printing bug with holey arrays

There was a bug where printing holey arrays would miss the delimiter & comma. This is now fixed, thanks to @amartin96.

After:

❯ bun -e 'console.log([1,,,2,1,3])' # After
[ 1, 2 x empty items, 2, 1, 3 ]

Before:

❯ bun-1.0.18 -e 'console.log([1,,,2,1,3])' # Before
[ 12 x empty items, 2, 1, 3 ]

New matcher in bun:test expect().toContainEqual()

We've added a new matcher to bun:test called toContainEqual. This is similar to toContain, but it uses toEqual to compare the values instead of ===.

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

test("toContainEqual", () => {
  expect("hello world").toContainEqual("hello");
  expect("hello world").not.toContainEqual("jello");
});

Thanks to @Electroid for implementing this.

Credits

We love seeing new contributors! Here are the 9 people who made their first contribution to Bun in this release:

Thanks to 18 contributors for making Bun v1.0.19 possible