Bun

Bun v1.1.9


Ashcon Partovi · May 22, 2024

Bun v1.1.9 is here! This release fixes 67 bugs (addressing 150 👍), and includes fixes to workspaces in bun install, sourcemaps in bun build, IPv6 & VPN connectivity issues, loading UNC paths, junctions, symlinks, and pnpm on Windows. fetch() gets faster. A new dns.prefetch() API is introduced. atob() gets 8x faster. buffer.toString('base64url') gets 5x faster. expect().toBeReturned() matcher is introduced. We've also made Node.js compatibility improvements and added Bun Shell fixes. Oh, and lots more bugfixes.

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

Previous releases

  • v1.1.8 fixes 54 bugs (addressing 184 👍). Support for process.on("uncaughtException") and process.on("unhandledRejection"), JSON.parse gets faster, Brotli support in node:zlib, [Symbol.dispose] in Bun APIs, fixes lots of crashes on Windows, and many other bugfixes.
  • v1.1.7 fixes 28 bugs (addressing 11 👍). Glob workspace names in bun install. Asymmetric matcher support in expect.extends() equals. bunx --version. Bugfixes to JSX transpilation, sourcemaps, cross-compilation of standalone executables, bun shell, RegExp, Worker on Windows, and Node.js compatibility improvements.
  • 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

Workspace fixes

This release includes several fixes to workspaces in bun install.

If you run into any more issues with workspaces, please open an issue and we'll investigate it!

Fixed: Workspace dependency "..." not found

We fixed a bug where running bun add to the workspace root, it would fail to add the dependency.

Fixed: Script not found "..."

We fixed a bug when running bun run in the script of a workspace would not resolve other scripts properly.

package.json
{
  "name": "monorepo",
  "workspaces": ["foo"]
}
foo/package.json
{
  "name": "foo",
  "scripts": {
    "prepare": "bun run start",
    "start": "echo 'Starting...'"
  }
}

Previously, bun add would fail with:

bun add bar
bun add
  ⚙️ bar [1/1] error: Script not found "start"

error: prepare script from "start" exited with 1

Now, bun add will work as expected:

bun add bar
bun add
  ⚙️ bar [1/1] success

Fixed: Incorrect number of Removed packages reported

A bug in the lockfile caused Bun to report an incorrect and sometimes inconsistent number of packages removed when running bun install/add/remove.

Fixed: Crash when non-existent --cwd is given to bun install

A bug where bun install --cwd=i-dont-exist would crash with an unhelpful error message has been fixed.

Big thanks to @dylan-conway for fixing these issues!

Fixed: Crash with different versions of bun install

We fixed a crash when it was possible to run bun install using a newer version of Bun, then running bun add with an older version of Bun.

# Run `bun install` using a newer version of Bun
bun upgrade --canary
bun install

# Run `bun add` with an older version of Bun
bun upgrade --stable
bun add lodash

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

Improved IPv4, IPv6, & VPN connectivity

A longstanding issue where Bun would fail to connect due to the server only broadcasting on either IPv4 or IPv6 has been fixed.

When your computer is connected to a dual-stack network, it is possible for DNS queries to return both IPv4 and IPv6 addresses. This often happened when using a VPN or connecting to localhost.

Previously, Bun would not handle this case and would fail to connect to the socket.

For the following input code:

using server = Bun.serve({
  hostname: "127.0.0.1",
  port: 0,
  fetch() { return new Response("👀😊👀"); }
});

const response = await fetch("http://localhost:" + server.port);
console.log(await response.text());

Bun now outputs:

❯ bun happy-eyeballs.js # New
👀😊👀

Previously, Bun would output:

❯ bun-1.1.8 happy-eyeballs.js # Old
ConnectionRefused: Unable to connect. Is the computer able to access the url?
 path: "http://localhost:64836/"

Bun v1.1.8 (macOS arm64)

Now, Bun implements a subset of the IETF standard "Happy Eyeballs" algorithm. If Bun receives multiple IP addresses for a single hostname, it will attempt to simultaneously connect to each address, then select the first successful connection.

Thanks to @gvilums for implementing this feature!

fetch() gets faster DNS resolution

Bun now supports asynchronous DNS resolution in fetch() requests.

This means that fetch() is faster in scenarios where your operating system does not use a DNS cache, or if you are sending requests to lots of different subdomains. Bun caches these DNS records in-memory, so that they are not re-resolved every time you make a fetch() request.

Thanks to @gvilums for implementing this feature!

Asynchronous DNS & Caching

Bun now automatically caches up to 255 DNS records in-memory for a maximum of 30 seconds. This reduces latency when making multiple requests to the same domain over a short period of time.

This applies to:

  • fetch() requests
  • node:http requests
  • node:https requests
  • Bun.connect requests
  • node:net requests
  • node:tls requests

This cache applies to both in-flight DNS requests and completed DNS requests. This means that if you make 50 fetch() requests to example.com at the same time, it will only resolve the DNS record once. If you wait 31 seconds and make another request, it will ask the operating system for the DNS record again.

This is useful for Docker Containers and environments where the operating system may not have an effective DNS cache.

New: dns.prefetch() API

You can also use the new dns.prefetch() API to prefetch DNS records before they are needed. This is useful if you want to pre-warm the DNS cache on startup.

import { dns } from "bun";

// ...on startup
dns.prefetch("example.com");

// ...later on
await fetch("https://example.com/");

This will prefetch the DNS record for example.com and make it available for use in fetch() requests.

New: dns.getCacheStats() API

To observe the DNS cache, you can use the new dns.getCacheStats() API.

import { dns } from "bun";

console.log(dns.getCacheStats());
//
// {
//   cacheHitsCompleted: 0,
//   cacheHitsInflight: 0,
//   cacheMisses: 0,
//   size: 0,
//   errors: 0,
//   totalCount: 0,
// }
//

await fetch("https://example.com/");

console.log(dns.getCacheStats());
// {
//   cacheHitsCompleted: 0,
//   cacheHitsInflight: 0,
//   cacheMisses: 1,
//   size: 1,
//   errors: 0,
//   totalCount: 1,
// }

atob() gets 8x faster

Thanks to @lemire's simdutf library, atob() is now 8x faster in Bun.

New: Added bytes() API to Web streams

Bun now supports the bytes() property on Web streams, which returns a Uint8Array of the stream's data. This is available on Request, Response, Blob, and Bun.file() objects.

const response = await fetch("https://example.com/");
const bytes = await response.bytes();
console.log(bytes); // Uint8Array(1256) [ 60, 33, ... ]

bytes() is a recent addition to the fetch() standard.

Thanks to @nektro for implementing this feature!

New: bun --no-clear-screen --watch

You can now use bun --no-clear-screen to disable the clearing of the screen when Bun is running in --watch or --hot mode. This is useful if you want to keep the terminal history after a file reloads.

You can also configure this using the BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD environment variable.

BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=1 bun --watch src/index.ts

New: expect().toHaveReturned() matchers

Bun now supports the expect().toHaveReturned() and expect().toHaveReturnedTimes(n) matchers, which are implemented by Jest.

import { expect } from "bun:test";

test("expect().toHaveReturned()", () => {
  const fn = jest.fn(() => "foo");
  fn();
  expect(fn).toHaveReturned();
  fn();
  expect(fn).toHaveReturnedTimes(2);
});

This has now been added thanks to @nektro.

Sourcemap improvements

Bun now loads sourcemaps after bun build --target=bun --sourcemap

When you build for production with bun build --target=bun --sourcemap, Bun will now load sourcemaps at runtime & use them to map stack traces to the original source code.

❯ bun out/sourca.js
1 | function mySource(hello: string) {
2 |   throw new Error("woopsie!!");
            ^
error: woopsie!!
      at mySource (/Users/jarred/Desktop/sourca.ts:2:9)
      at /Users/jarred/Desktop/sourca.ts:5:1

Bun v1.1.9 (macOS arm64)

Previously:

❯ bun-1.1.8 out/sourca.js
1 | // @bun
2 | // sourca.ts
3 | var mySource = function(hello) {
4 |   throw new Error("woopsie!!");
            ^
error: woopsie!!
      at mySource (/Users/jarred/Desktop/out/sourca.js:4:9)
      at /Users/jarred/Desktop/out/sourca.js:6:1

Bun v1.1.8 (macOS arm64)

Thanks to @paperdave.

Fixed: Sourcemaps off-by-24-bytes error

At runtime, Bun reserves the first 24 bytes of the internal source map buffer for metadata about the sourcemap.

This behavior was only meant to occur at runtime, but it was mistakenly enabled in bun build too. This caused sourcemaps to be off by 24 bytes when using bun build --target=bun --sourcemap.

This has now been fixed thanks to @paperdave.

Bundows improvements

In the module resolver code, Bun thought that junctions and symlinks were regular files instead of directories. This caused issues when using monorepos on Windows or when using the pnpm package manager with Bun.

The following code now works as expected on Windows:

import { version } from "lodash";
console.log(version); // 4.17.21

Previously:

error: Unexpected reading 'C:\\abcd\\node_modules\\lodash'

Thanks to @paperdave.

Fixed: Loading UNC paths in Windows

When a UNC path was used in various parts of Bun such as require("\\wsl.local\\path"), it would fail with an assertion failure about storing posix paths in Windows. This assertion failure was an internal error in Bun, and has now been fixed.

This bug impacted:

  • bun install
  • bun run <file.ts>
  • bun <file.ts>

And more.

Thanks to @paperdave.

Node.js compatiblity improvements

Fixed: msw seems to work now

We fixed a bug in node:http where the statusCode property was read-only, which could cause an error when trying to set the status code. This was done by packages, such as msw, which now work in Bun.

import axios from "axios";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";

const server = setupServer(
  ...[
    http.get("https://example.com/", () => {
      return HttpResponse.json({ msw: "works!" });
    }),
  ],
);
server.listen();
const response = await axios.get("https://example.com/");
console.log(response.data.msw); // "works!"

This has now been fixed thanks to @nektro.

New: Added shake128 and shake256 algorithms to node:crypto

Bun now supports the shake128 and shake256 algorithms in node:crypto.

import { createHash } from "node:crypto";

const hash = createHash("shake256");
hash.update("hello");
console.log(hash.digest());

This has now been fixed thanks to @nektro.

buffer.toString("base64url") gets 5x faster

Again, thanks to the simdutf library, toString("base64url") is now 5x faster in Bun.

Fixed: http.Agent not working without new

We fixed a bug where you could not construct an http.Agent without using the new keyword. Some older Node.js APIs, such as http.Agent existed before JavaScript introduced classes, and so they did not require the new keyword.

import { Agent } from "node:http";

const agent = Agent();

This has now been fixed thanks to @nektro.

Fixed: newListener event is emitted in correct order

We fixed an issue where the newListener event was being emitted in the wrong order, which could cause issues with some libraries. Previously, the newListener event was emitted after the listener was added, but now it is emitted before the listener is added.

import { expect, mock } from "bun:test";
import { Stream } from "node:stream";

test("newListener event is emitted in correct order", () => {
  const stream = new Stream();
  const cb = mock((event) => {
    expect(stream.listenerCount(event)).toBe(0);
  });
  stream.on("newListener", cb);
  stream.on("foo", () => {});
  expect(cb).toHaveBeenCalled();
});

Fixed: Improved argument parsing in net.connect()

We fixed various bugs where the argument parsing in net.connect() would not match the behavior of Node.js. For example, when a port and host are provided, or only an options object is provided, the port and host arguments would be ignored.

import { connect } from "node:net";

const socket = connect({
  port: 443,
  host: "localhost",
  rejectUnauthorized: false,
});

socket.on("secureConnect", () => {
  console.log("Connected!");
  socket.end();
});

Fixed: Promisified child_process.execFile() returned wrong value

We fixed a bug where the execFile() function was not returning the correct value when using the util.promisify() function.

import { execFile } from "node:child_process";
import util from "node:util";

const execFileAsync = util.promisify(execFile);
const result = await execFileAsync("ls");

console.log(result);
// Before: 'src\n'
// After: { stdout: 'src\n', stderr: '' }

Fixed: Crash using which with long path

We fixed a bug where Bun.which() would crash when given a long path. Instead of crashing, it now throws an error: bin path is too long.

import { which } from "bun";

which("a".repeat(100000));
// Before: <crash>
// After: <error: bin path is too long>

Thanks to @zackradisic for fixing this issue.

Fixed: Alias debuglog to debug

We fixed a bug where util.debuglog() was not being aliased to util.debug(). Thanks to @gaurishhs for fixing this issue.

import { debuglog } from "node:util";

debuglog("foo");

Fixed: fs.Dirent now has path & parentPath properties

We fixed a bug where the path property and the new parentPath properties were not being set on fs.Dirent objects. This could cause issues when using fs.Dirent objects in conjunction with fs.readdir().

import { readdirSync } from "node:fs";

readdirSync("src").forEach((dirent) => {
  console.log(dirent.path);
  // Before: undefined
  // After: 'src'
});

This has now been fixed thanks to @nektro.

Fixed: must be a number or a Date

We fixed a bug where certain APIs that interpret arguments as double would throw an error due to incorrect integer coercion.

import { utimesSync } from "node:fs";

const atime = Math.floor(Date.now() / 1000);
const mtime = Math.floor(Date.now() / 1000);
utimesSync("hello.txt", atime, mtime);

// Before: Error: Argument must be a number or a Date
// After: <no error>

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

Bun Shell improvements

Escaping more characters

We fixed bugs where special characters, such as |, &, and ;, that need to be escaped when used in a shell, but were not being escaped.

bun shell 'echo "Hello, world!" | grep "world"'
# Hello, world!
bun shell 'echo "Hello, world!" | grep "world"'
# Hello, world!

Thanks to @zackradisic for fixing these issues.

Tilde expansion in Bun shell

The Bun shell now supports tilde expansion, which means you can use ~ (aka. tilde) to refer to your home directory. For example, ~/Documents will expand to the path of $HOME/Documents.

This is supported on all platforms, including Windows.

echo ~
# /Users/bun

echo ~/Documents
# /Users/bun/Documents

Thanks to @zackradisic for implementing this feature.

Fixed: cd would return the wrong exit code

We fixed a bug where cd would return the wrong exit code run in the Bun shell. This would happen when there was an error changing directories.

cd /not/a/directory
echo $?
0

Fixed: Crash when pasting very large input in argv0

A bug where pasting too large input into the first argument of the Bun shell would crash has been fixed.

bun exec ${"a".repeat(100_000_000)}

The bug was caused by a missing bounds check in which. Operating systems have a maximum length of 512, 1024, or 4096 bytes for file paths and when that limit is exceeded Bun would crash.

This has now been fixed thanks to @zackradisic.

Thanks to 16 contributors!