Bun

Bun v0.5.8


Ashcon Partovi · March 18, 2023

Bun v0.5.8 introduces expect() snapshots in bun test, preloading modules using bun --preload, improvements to Web API & Node.js compatibility, lots of bug fixes, and a teaser of what's next.

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

We're hiring C/C++ and Zig engineers to build the future of JavaScript!

Snapshot testing with bun test

bun test aims to be a high-performance, drop-in replacement for Jest. Now it has support for snapshot testing, using expect(value).toMatchSnapshot(), which supports the same snapshot format as Jest. Here's how it works:

First, create a test.

snapshot.test.ts
import { test, expect } from "bun:test"; // "bun:test" imports are optional

test("can test a snapshot", async () => {
  const response = await fetch("https://example.com/");
  const body = await response.text();
  expect(body).toMatchSnapshot();
});

Then, run the test file using bun test.

bun test snapshot.test.ts

Next, you'll see the following output in your terminal.

 snapshot.test.ts:
can test a snapshot

1 pass
0 fail
snapshots: +1 added
 1 expect() calls
Ran 1 tests across 1 files [48.00ms]

Finally, you'll see there is a newly-generated snapshot file. Bun uses the same snapshot format as Jest, which means you don't need to re-generate your snapshots when migrating from Jest to bun test.

snapshots /snapshot.test.ts.snap
exports[`can test a snapshot 1`] = `
"<!doctype html>
<html>
<head>
    <title>Example Domain</title>
</head>
</html>
"
`;

If you need to update your snapshots, you can pass the --update-snapshots flag, which will update snaphots for every test file that is run.

bun test --update-snapshots

In addition to toMatchSnapshot(), Bun now supports these new matchers:

We also added a flag to re-run a test file multiple times, to help with reproducing errors in flaky tests.

bun test --rerun-each=5

This reruns each test file 5 times.

bun --preload <preload> [command]

Bun can now preload files before running a file or a test suite. This behavior is analogous to node --require.

To preload a file via the --preload flag:

bun --preload ./preload.ts run ./index.ts

To configure a list of files to preload in bunfig.toml:

bunfig.toml
preload = ["./preload.ts"]

This is useful for registering plugins, injecting globals, or performing setup tasks without cluttering your scripts or entrypoints with extra import() or require() statements.

Here's an example of how to preload a Bun.plugin() to load .yml files.

index.ts
config.yml
yaml.plugin.ts
index.ts
import config from "./config.yml";

console.log(config);
config.yml
enabled: true
yaml.plugin.ts
import { plugin } from "bun";

plugin({
  name: "YAML",
  async setup(build) {
    const { load } = await import("js-yaml");
    const { readFileSync } = await import("fs");

    // when a .yaml file is imported...
    build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => {

      // read and parse the file
      const text = readFileSync(args.path, "utf8");
      const exports = load(text) as Record<string, any>;

      // and returns it as a module
      return {
        exports: {default: exports, ...exports},
        loader: "object", // special loader for JS objects
      };
    });
  },
});

You can now import .yml files from your entrypoint:

bun --preload ./yaml.plugin.ts index.ts
{
  enabled: true
}

To avoid the --preload flag, configure bunfig.toml:

bunfig.toml
preload = ["./yaml.plugin.ts"]

Running bun test or bun run will now preload the plugin automatically.

bun run index.ts
{
  enabled: true
}

Partial glob support in bun install workspaces

The following pattern is now supported in "workspaces" with bun install:

{
  "name": "myWorkspace",
  "workspaces": [
    // Make all folders with a package.json and a name in packages/ part of the workspace
    "packages/*"
  ]
}

You can use a /* to add all folders matching the prefix into your workspace. This is useful for monorepos that have many packages.

For now, only a single glob pattern at the very end (foo/*) is supported. We plan to improve our glob support in the future. In the meantime, we also added better error messages when a glob pattern isn't implemented yet.

Web Compatibility

Thanks to improvements to Bun's test coverage, we've fixed a number of bugs in Bun's Web API compatibility.

request.bodyUsed & response.bodyUsed

request.bodyUsed and response.bodyUsed were inconsistent with the spec when using null or undefined bodies.

const response = new Response(null);

await response.arrayBuffer();

console.log(response.bodyUsed);

/*

Incorrect:

   Bun v0.5.7: true

Correct:

   Bun v0.5.8: false
       Chrome: false
       Safari: false
*/

Consuming Request & Response bodies

null or undefined Request & Response bodies can now be consumed multiple times, which aligns the behavior with Chrome & Safari.

Previously, Bun would throw an error when trying to consume a null or undefined body twice:

bun-0.5.7 response.js
1 | const response = new Response(null);
2 | await response.arrayBuffer();
3 | console.log(await response.arrayBuffer());
                ^
error: Body already used

Now .arrayBuffer() on null or undefined body returns an empty ArrayBuffer instead:

bun response.js

ArrayBuffer(0) [ ]

/*

Incorrect:

   Bun v0.5.7: <error> "Body already used"

Correct:

   Bun v0.5.8: ArrayBuffer(0) [ ]
       Chrome: ArrayBuffer(0) [ ]
       Safari: ArrayBuffer(0) [ ]
*/

Similarly, Response.prototype.body is supposed to return null when there is no body, but Bun was returning an empty ReadableStream instead.

const response = new Response(null);
console.log(response.body);

/*

Incorrect:

   Bun v0.5.7: ReadableStream { ... }

Correct:

   Bun v0.5.8: null
       Chrome: null
       Safari: null
*/

blob.slice() contentType argument

blob.slice accepts an optional third argument for the type of the new Blob. Previously, Bun didn't support this argument.

const blob = new Blob(["Hello, world!"], { type: "text/plain" });
const slice = blob.slice(0, 5, "text/html; charset=utf-8");
console.log(slice.type); // "text/html; charset=utf-8"

/*

Incorrect:

   Bun v0.5.7: ""

Correct:

   Bun v0.5.8: "text/html; charset=utf-8"
       Chrome: "text/html; charset=utf-8"
       Safari: "text/html; charset=utf-8"
*/

Request constructor

The Request constructor in Bun had a couple bugs.

When given two arguments, it always expected the first argument to be a string, which is incorrect.

// This didn't work: (inconsistent with the spec)
new Request(new Request("https://example.com"), {});

// But any of these did:
new Request(new Request("https://example.com"));
new Request("https://example.com", { method: "POST" });
new Request("https://example.com", {});

That has been fixed, and now the first argument can be a string or a Request object.

// This works now:
console.log(new Request(new Request("https://example.com"), {}).url);

// "https://example.com"

Another bug: the url argument was not being validated correctly (validated in fetch() but not in new Request())

// this is supposed to throw:
const request = new Request("", { method: "POST" });

Now it (correctly) throws an error:

bun /tmp/app.js
1 | const request = new Request("", { method: "POST" });
                   ^
error: Failed to construct 'Request': url is required.

Bun's test coverage for the Request constructor was lacking and Bun's implementation of fetch (which does validate) doesn't use the same code path as the Request constructor.

Node.js compatibility

Bun continues to make improvements to Node.js compatibility. Here's what has changed since the last release.

Bun.sleepSync()

To fix an inconsistency issue between Bun.sleep() and Bun.sleepSync(), there is a breaking change where Bun.sleepSync() now accepts milliseconds, instead of seconds. Using code search on GitHub, we found that most usages were already assuming the units were milliseconds.

Bun.sleepSync(1); // sleep for 1 ms (not recommended)
await Bun.sleep(1); // sleep for 1 ms (recommended)

What's next?

For Bun v0.6, you can expect big changes, including a brand new bundler!

Bun's upcoming bundler intends to make building modern, full-stack JavaScript and TypeScript apps a lot simpler (and faster).

Of course, we continue to make progress towards our priorities of Node.js compatibility, overall stability, and performance as Bun inches closer to a 1.0 release. To measure progress towards these goals we've dramatically increased our test coverage of Bun, Node.js, and standard-Web APIs. Stay tunned.

Changelog

#2156Fixed {Request,Response}.body not being null by @cirospaciari
#2115Implemented os.cpus() for macOS by @jwhear
#2161Fixed HTMLRewriter.onDocument() not being run by @jwhear
#2144Fixed incorrect return value for dns.{resolve4,resolve6}() by @cirospaciari
599f63cSupported older releases of macOS, including 10.15 by @Jarred-Sumner
0a7309cImproved performance of EventEmitter by @Jarred-Sumner
eb94e5bChanged BoringSSL to use heap from mimalloc by @Jarred-Sumner
#2192Improved stability of bun pm ls by @alexlamsl
693be3dImproved performance of ASCII case-insensitive comparisons by @Jarred-Sumner
#2202Fixed ANSI escape codes not being piped from stdout to a file by @alexlamsl
5d296f6Fixed an issue where shasum was being parsed twice by @Jarred-Sumner
#2213Fixed an issue with bun install and duplicate dependencies by @alexlamsl
#2142Implemented os.networkInterfaces() by @jwhear
#2191Fixed incorrect port for bun dev by @xjmdoo
#2143Fixed issues with fetch({ signal }) on shutdown by @cirospaciari
#2223Implemented http.listen({ signal }) by @cirospaciari
#2222Implemented http.get({ signal }) by @cirospaciari
#2226Removed transient error when sqlite was given an empty statement by @ThatOneBro
#2231Implemented bun --preload <file> by @Jarred-Sumner
#2249Fixed issue with Bun.file().arrayBuffer() on an empty file by @cirospaciari
#2258Fixed bug where http.request() did not accept URLs by @ThatOneBro
#2262Improved compatibility of http.request() when method is GET or HEAD and has a body by @ThatOneBro
#2242[BREAKING] Bun.sleepSync() now takes milliseconds, instead of seconds by @jwhear
#2276Fixed os.tmpdir() from removing trailing slashing by @cirospaciari
#2285Fixed request.url not including search params in node:http by @zhiyuang
#2295Changed bunx without parameters to show a help page by @Zeko369
#2288Fixed headers not being lowercase in node:http by @ThatOneBro
#2293Fixed bunx from not being able to find scoped npm packages by @Zeko369
#2302Fixed an issue where "latest" would not be considered in semver range by @alexlamsl
#2307Fixed bun install not reporting connection errors to the npm registry by @alexlamsl
#2314Implemented expect().toThrow(RegExp) by @alexlamsl
#2315Fixed error messages for Blob on Linux by @alexlamsl
#2320Implemented some of tty.WriteStream for process.{stdout,stderr} by @ThatOneBro
0a9cb0eFixed issues when HTMLRewriter would receive an empty string by @Jarred-Sumner
#2331Fixed parameter errors with crypto.scryptSync() by @dylan-conway
#2341Improved Buffer compatibility with Node.js by @alexlamsl
e16053cFixed an issue where crypto.createHash().update("binary") would not work by @Jarred-Sumner
#2340Improved spec-compliance of Blob.type by @Jarred-Sumner
#2371Fixed error when require.resolve() argument is empty by @paperdave
#2337Implemented unix socket support in net.Server by @cirospaciari
4c38798Fixed incorrect encoding of Socket.remoteAddress by @Jarred-Sumner
27f5012Fixed node:https from being readonly by @Jarred-Sumner
#2389Implemented expect().toBeInstanceOf() by @zhiyuang
e613b50Implemented missing constants in node:zlib by @Jarred-Sumner
#2294Implemented expect().toMatchSnapshot() by @dylan-conway
#2397Fixed an issue where parsing a malformed bun.lockb by @alexlamsl
#2400Fixed an issue where Bun.serve() could not listen on IPv6 by @cirospaciari
9a5f78fFixed console.log() not showing [Getter] values by @Jarred-Sumner
#2404Implemented expect().toMatch(RegExp) by @zhiyuang

Also, if you haven't heard, Bun has docs now!

A huge thanks to @colinhacks, as well as the many folks that have contributed since the last release: @jakeboone02, @charliermarsh, @BrettBlox, @damianstasik, @Sheraff, @johnnyreilly, @fdaciuk, @akash-joshi, @TommasoAmici, @raxityo, @DreierF, @rmorey, @cunzaizhuyi, @rodoabad, @gaurishhs, @maor-benami, @aabccd021, @pfgithub, @bushuai, @Zeko369, @noahmarro, @harisvsulaiman, @nskins, @milesj, and @jsoref.