Bun

Bun v1.1.4


Jarred Sumner · April 16, 2024

Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager — all in one.

Bun v1.1.4 fixes 40 bugs (addressing 84 👍 reactions). bun run --filter <workspace> <script> lets you run multiple workspace scripts in parallel. Reinstalls get up to 50% faster in bun install. Important reliability fixes to bun install. bun:sqlite supports using for resource cleanup and has a few bugfixes. Memory leak impacting Next.js Standalone & Web Streams is fixed. Node.js compatibility improvements fs and child_process. Fix for "Connection closed" in fetch(). A few bugfixes for Bundows.

Previous releases

  • v1.1.3 Bun v1.1.3 fixes 5 bugs. bun install gets 50% faster on Windows. A bug that could cause bun install to hang in rare cases has been fixed. A bug where some errors in bun install would not produce a non-zero exit code has been fixed. A bug that could cause specific certain combinations of dependencies to fail to install has been fixed. A bug on Windows where CTRL + C on cmd.exe after exiting bun would not behave as expected has been fixed. A bug where missing permissions on Windows for reading a directory could lead to a crash has been fixed.
  • v1.1.2 Bun v1.1.2 fixes 4 bugs (addressing 44 👍 reactions). EBUSY on Windows in vite dev, next dev, and saving bun.lockb has been fixed. Bun Shell gets support for seq, yes, basename and dirname. A TypeScript parsing edgecase has been fixed. A bug causing 'unreachable code' errors has been fixed. fs.watch on Windows has been rewritten to improve performance and reliability.
  • v1.1.1 Bun v1.1.1 fixes 20 bugs (addressing 60 👍 reactions).
  • v1.1.0 Bundows. Windows support is here! Plus, JSON IPC Node <-> Bun.

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

bun install fixes

bun --filter runs workspace scripts in parallel

Say you have a monorepo with two packages: packages/api and packages/frontend, both with a dev script that will start a local development server.

Normally, you would have to open two separate terminal tabs, cd into each package directory, and run bun dev:

Using --filter, you can run the dev script in both packages at once, using glob patterns to filter which projects to run the script in:

bun --filter='*' dev

Both commands will be run in parallel, and you will see a nice terminal UI showing their respective outputs:

You can pass multiple filters to --filter:

bun --filter 'packages/api' --filter 'packages/frontend' dev

You can think of this as a more powerful replacement for npm run --workspace <workspace> <script>.

Thanks to pnpm for the inspiration, and to @gvilums for implementing this feature.

Up to 50% faster reinstalls in bun install

When dependencies in node_modules have to be reinstalled (for example, because the version on-disk is different), bun install is now up to 50% faster.

In a repository with 450 MB of node_modules on a Macbook Pro M3:

❯ hyperfine "bun install --ignore-scripts" "bun-1.1.3 install --ignore-scripts" --prepare="rm -rf node_modules/**/package.json" --warmup=2
Benchmark 1: bun install --ignore-scripts
  Time (mean ± σ):     501.6 ms ±  15.6 ms    [User: 18.0 ms, System: 1174.0 ms]
  Range (min … max):   491.1 ms … 543.8 ms    10 runs

Benchmark 2: bun-1.1.3 install --ignore-scripts
  Time (mean ± σ):     771.4 ms ±  13.4 ms    [User: 13.9 ms, System: 616.6 ms]
  Range (min … max):   756.0 ms … 800.4 ms    10 runs

Summary
  bun install --ignore-scripts ran
    1.54 ± 0.05 times faster than bun-1.1.3 install --ignore-scripts

We moved deleting the old package's files to a separate thread to speed up the wall-clock time and reduce blocking I/O on the main thread.

Fixed: Hoisting aliased packages

Bun was not hoisting aliased npm packages before. This meant we would install the same aliased packages multiple times in node_modules unnecessarily.

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

Fixed: Sometimes flaky bun install in CI environments

A bug that could cause bun install to fail in CI environments sporadically has been fixed.

The code that waited for dependencies in node_modules of dependencies within a child node_modules folder was not correctly always waiting for the parent to finish installing before installing the children. This led to potentially creating the parent dependencies folder in node_modules, installing the package, and then deleting it.

This bug manifested most frequently when installing many different versions of the same package simultaneously when there was no cached version available on disk. For example, this bug could cause string-width to potentially throw an ERR_REQUIRE_ESM when used in Node.js.

Thanks to @dylan-conway for fixing this.

Node.js compatibility improvements

fs.close() callback is optional

Previously, the following code would work in Node.js but not in Bun:

import fs from "fs";

fs.open("file.txt", "r", (err, fd) => {
  // missing the callback in the 2nd argument!
  fs.close(fd);
});

Now, the callback is optional in Bun as well.

Thanks to @gvilums for fixing this.

fs.writeFile(fd, data) should not truncate

In Node.js, when fs.writeFile is called with a file descriptor, it does not truncate the file. When called with a path, it does.

Previously in Bun, fs.writeFile would truncate the file when called with a file descriptor or a path. This has been fixed to now match Node.js behavior and only truncate when called with a path.

import { writeFile } from "fs/promises";
import { openSync } from "fs";

const fd = openSync("file.txt", "w");
await writeFile(fd, "hello"); // does not truncate
await writeFile("file.txt", "hello"); // truncates

Thanks to @gvilums for fixing this.

FileHandle#appendFile appends

In Node.js, FileHandle#appendFile appends to the file. Previously in Bun, it would truncate the file before appending. This has been fixed.

import { open } from "fs/promises";

const file = await open("file.txt", "a");
await file.appendFile("hello"); // appends

bun:sqlite improvmeents

New: using Database & Statement

The using syntax is now supported for Database and Statement objects in bun:sqlite. This syntax will automatically call Database#close and Statement#finalize when the scope ends.

import { Database } from "bun:sqlite";

{
  // Automatically close the Database at the end of this scope
  using db = new Database("file.db");

  // Automatically finalize the statement at the end of this scope.
  using query = db.query("SELECT * FROM users");
  for (const row of query.all()) {
    console.log(row);
  }
}

using is part of the stage3 TC39 proposal Explicit Resource Management.

New: Database#fileControl method

The low-level sqlite3_file_control method is now exposed in bun:sqlite as Database#fileControl.

import { Database, constants } from "bun:sqlite";

const db = new Database();
// Ensure WAL mode is NOT persistent
// this prevents wal files from lingering after the database is closed
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);

New: Database#close(throwOnError: boolean = false) argument

You can now pass a throwOnError argument to Database#close to throw an error if the database cannot be closed for any reason.

import { Database, constants } from "bun:sqlite";

const db = new Database();
const query = db.query("SELECT * FROM users");
db.close(true); // "Database is locked"
db.close(false); // no error, stop new queries from being created

Internally, when throwOnError is true, sqlite3_close is called instead of sqlite3_close_v2.

This is not a breaking change because previously close did not accept any arguments, and the behavior remains the same if no arguments are passed. The argument defaults to false.

Fixed: Close database when garbage collected

When the Database object gets garbage collecte due to no longer being in use, the Database will now be closed in sqlite automatically.

Previously, bun:sqlite would only close databases automatically on process exit. This was not good enough.

Fixed: Bug with binding to duplicate column names in JOIN

A longstanding bug related to duplicate column names in JOIN queries when using bun:sqlite has been fixed.

import { Database } from "bun:sqlite";

const db = new Database();

db.query(
  "CREATE TABLE Users (Id INTEGER PRIMARY KEY, Name VARCHAR(255));",
).run();

db.query(
  "CREATE TABLE Cars (Id INTEGER PRIMARY KEY, Driver INTEGER, FOREIGN KEY (Driver) REFERENCES Users(Id))",
).run();

db.query('INSERT INTO Users (Id, Name) VALUES (1, "Alice");').run();
db.query("INSERT INTO Cars (Id, Driver) VALUES (1, 1);").run();

const car = db.query("SELECT * FROM Cars JOIN Users ON Driver=Users.Id").get();

console.log(car);

db.close();

Previously, the above code would incorrectly log:

{
  Id: 1,
  Driver: 1,
  Name: 1,
}

Now, it correctly logs:

{
  Driver: 1,
  Id: 1,
  Name: "Alice",
}

This matches the behavior of the popular better-sqlite3 package.

Thanks to @gvilums for fixing this, and to @Hanaasagi for the initial investigation.

Bundows improvements

windowsHide & windowsVerbatimArguments

The Node.js child_process API supports the windowsHide and windowsVerbatimArguments options. These options are now supported in Bun as well, thanks to @nektro.

Bun.serve() extra byte

A bug where Bun.serve() could potentially send an extra byte of duplicated data with certain input has been fixed, thanks to @cirospaciari.

This regression started in Bun v1.1.1 and we've improved our test coverage to prevent this from happening again.

Bun.which() case-insensitive file extension

On Windows, filenames are generally case-insensitive. Bun.which() now also matches case-insensitive file extensions on Windows. This means you can open cmd.EXE as well as cmd.exe (and all other permutations of case) on Windows.

--cwd flag works on Windows

The --cwd flag now works as expected on Windows.

bun --cwd=.\my-app run hey

This changes the current working directory to .\my-app before running the hey script. This saves you from having to cd into the directory before running the script.

Runtime improvements

New: AbortSignal.any

Bun now supports AbortSignal.any, which lets you create a new AbortSignal that is aborted when any of the passed AbortSignal are signaled. It's sort of like Promise.race for AbortSignals.

const first = new AbortController().signal;
const second = new AbortController().signal;
fetch("https://example.com", {
  signal: first,
});
fetch("https://example.com", {
  signal: second,
});

const abortSignal = AbortSignal.any([first, second]);

// Cancel this when either `first` or `second` is aborted
await fetch("https://example.com/slow", { signal: abortSignal });

This code comes directly from WebKit/Safari, so thanks to the WebKit team for implementing this feature.

Fixed: Memory leak in Web Streams impacting Next.js

A memory leak in Web Streams has been fixed. This bug could cause Next.js Standalone to leak memory when responding to requests.

Thanks to @lithdew for fixing this.

Fixed: fetch() "Connection closed" bug

Some servers rely on TLS v1.2 renegotiation, which lets a server change how it authenticates a client after the connection has been established. This was deprecated in TLS v1.3, but many servers still use it. Previously, when encountering a server that used TLS renegotiation, Bun would throw a "Connection closed" error. We now allow TLS renegotiation for clients, but continue to not allow it for servers. For servers, TLS renegotiation is a DoS attack vector, BoringSSL does not support it, and is generally not recommended.

This bug sometimes manifested as receiving a Connection closed error when using fetch() in Bun.

Thanks to @cirospaciari for fixing this, and to @lithdew for narrowing down the issue.

Fixed: escaped newlines in shell commands

A bug where escaped newlines in shell commands did not match bash's behavior has been fixed, thanks to @zackradisic. This bug impacted bun run on Windows as well as Bun.$ at runtime. Thanks to @zackradisic for fixing this.

Thanks to 11 contributors!