Bun

Bun v0.7.0


Jarred Sumner · July 21, 2023

We're pleased to announce Bun v0.7.0, a big leap forward in terms of Node.js compatibility.

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

Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager — all in one. Over the past couple months, we've been releasing a lot of changes to Bun recently, here's a recap in case you missed it:

  • v0.6.10 - fs.watch(), bun install bug fixes, bun test features, and improved CommonJS support
  • v0.6.11 - Addressed a release build issue from v0.6.10.
  • v0.6.12 - Sourcemap support in Error.stack, Bun.file().exists(), and Node.js bug fixes.
  • v0.6.13 - Implemented mock Date, faster base64 encoding, and fixes for WebSocket and node:tls.
  • v0.6.14 - process.memoryUsage(), process.cpuUsage(), process.on('beforeExit', cb), process.on('exit', cb) and crash 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

Vite support

Support is still experimental and non-optimized. Vite does not use Bun's bundler, module resolver, or transpiler, even when run with Bun.

With the recent strides towards Node.js API compatibilty, Bun can now run vite dev, thanks to @paperdave! This is one of Bun's most upvoted issues.

To try this with one of Vite's starter projects, use bunx:

bunx create-vite myapp
cd myapp
bun install

Then start the dev server.

bun --bun vite dev

Why --bun? The --bun flag tells Bun to override the #! /usr/bin/env node shebang in the vite CLI and execute the file with Bun instead of Node.js. In a future release this will be the default behavior.

Hot module reloading with Vite

This is a great way to develop frontend code with Bun's APIs on the server when building frontend apps.

Note: if you run bun vite dev without -b or --bun, it will still run in Node.js as vite's CLI specifies #!/usr/bin/env node at the top which tells Bun (and other software on your computer) to run it in Node.js.

Concurrency with Worker

Bun now supports Worker which allows you to run another JavaScript instance in a separate thread. In Bun, workers support ES Modules, CommonJS, TypeScript, JSX, and the rest of Bun's features with no extra configuration.

As in browsers, Worker is a global class. To create a worker from the main thread:

main.ts
const worker = new Worker("./worker.ts");
worker.addEventListener("message", (event: MessageEvent) => {
  console.log("Message from worker:", event.data);
});
worker.postMessage("Hello from main thread!");

On the worker thread:

worker.ts
addEventListener("message", (event: MessageEvent) => {
  console.log("Message from main thread:", event.data);
  postMessage("Hello from worker thread!");
});

This release does not include support for the node:worker_threads module, but this unblocks the work necessary for us to implement it in Bun.

The following globals have been added to Bun:

  • postMessage
  • addEventListener
  • removeEventListener
  • onmessage (getter/setter)

Refer to Docs > API > Workers to learn more about using Worker in Bun.

The popular comlink package works in Bun without changes. This library makes it easier to share functions and state between main and worker threads.

Comlink usage example

structuredClone() support

As in browsers, postMessage serializes messages using the structured clone algorithm. Bun now exposes this via the Web-standardstructuredClone() function, which provides a mechanism for deep-cloning objects. It is similar to JSON.parse(JSON.stringify(obj)), but it supports more types.

const obj = { a: 1, b: 2 };
const clone = structuredClone(obj);

AsyncLocalStorage support

Bun now implements AsyncLocalStorage from the node:async_hooks module. This provides a mechanism for passing contextual data through a chain of asynchronous code. This is a big step towards supporting Next.js and other frameworks that rely on this module.

import { AsyncLocalStorage } from "node:async_hooks";

const requestId = new AsyncLocalStorage();
let lastId = 0;

Bun.serve({
  fetch(request) {
    lastId++;
    // Run the callback with 'requestId' set. async_hooks will preserve
    // this value through any chain of asynchronous code.
    return requestId.run(lastId, async () => {
      console.log(`Request ID: ${requestId.getStore()}`);
      await Bun.sleep(500);
      // Even if new requests mutate 'lastId', 'requestId' is still preserved.
      return new Response(`Request ID: ${requestId.getStore()}`);
    });
  },
});

Reduce memory usage with bun --smol

bun --smol is a new CLI flag which configures the JavaScriptCore heap size to be smaller and grow slower, at a cost to runtime performance. This is useful for running Bun in memory-constrained environments.

To avoid setting the flag manually, you can set this as a default in bunfig.toml.

bunfig.toml
smol = true

[test]
# set it only for tests, if you want
smol = true

--bail in bun test

Running bun test with --bail=1 will exit after the first test failure.

bun test --bail 1
bun test v0.7.0

✓ test1 [0.02ms]
test2.test.js:
1 | import {test, expect} from 'bun:test';
2 |
3 | test('test2', () => {
4 |   expect(2).toEqual(3);
      ^
error: expect(received).toEqual(expected)
Expected: 3
Received: 2
      at /Users/colinmcd94/Documents/bun/fun/test/test2.test.js:13:8
✗ test2 [0.18ms]
Ran 2 tests across 2 files. [8.00ms]
Bailed out after 1 failures

This is useful for CI environments or when you want to stop running tests after the first failure. Thanks to @TiranexDev for landing this improvement!

Bun.readableStreamToFormData()

Bun now exposes a helper for converting a ReadableStream into FormData.

It supports multipart form data.

import { readableStreamToFormData } from "bun";

// without dashes
const boundary = "WebKitFormBoundary" + Math.random().toString(16).slice(2);

const myStream = getStreamFromSomewhere(); // ...
const formData = await Bun.readableStreamToFormData(stream, boundary);
formData.get("foo"); // "bar"

It also supports URL-encoded form data:

import { readableStreamToFormData } from "bun";

const stream = new Response("hello=123").body;
const formData = await readableStreamToFormData(stream);
formData.get("hello"); // "123"

We added this to help fix a bug causing request.formData() and response.formData() to hang when the body was a ReadableStream from JavaScript.

serialize and deserialize in bun:jsc

The bun:jsc module now exports serialize() and deserialize(), which convert JavaScript objects to ArrayBuffer and back.

import { serialize, deserialize } from "bun:jsc";
import { deepEquals } from "bun";

const obj = { a: 1, b: 2 };
const buffer = serialize(obj);
const clone = deserialize(buffer);

if (deepEquals(obj, clone)) {
  console.log("They are equal!");
}

The node:v8 module exports these same functions, for compatibility with existing libraries that serialize/deserialize data between processes.

WebSocket improvements

You can now manually send & receive WebSocket ping and pong frames.

const ws = new WebSocket("wss://echo.websocket.org");
ws.addEventListener("pong", () => {
  console.log("Received pong");
});
ws.ping();

This applies to both ServerWebSocket and WebSocket.

nodebuffer is now the default binaryType

By default, the binaryType for WebSocket and ServerWebSocket in Bun is now nodebuffer This means that binary data frames in WebSocket will be Buffer instances, instead of ArrayBuffer (as before). This is to match the bahavior of the ws package.

const ws = new WebSocket("wss://echo.websocket.org");

ws.addEventListener("message", (event: MessageEvent) => {
  console.log(event.data instanceof Buffer); // true
});

To change it back to ArrayBuffer, set ws.binaryType = "arraybuffer".

const ws = new WebSocket("wss://echo.websocket.org");
ws.binaryType = "arraybuffer";

ws.addEventListener("message", (event: MessageEvent) => {
  event.data; // ArrayBuffer
});

(Note that in browsers it is Blob by default.)

Close reasons propagate correctly now

A bug was fixed where WebSocket would not propagate close reasons from third-party servers correctly. Thanks to @Electroid for landing these improvements!

Node.js compatibility improvements

This release adds several additional improvements to Node.js compatibility.

Improvements to TLSSocket from node:tls

The following methods were implemented on the TLSSocket class. Thanks to @cirospaciari for landing these improvements in #3596.

  • .getPeerFinished()
  • .getFinished()
  • .getProtocol()
  • .getSharedSigalgs()
  • .isSessionReused()
  • .exportKeyingMaterial()
  • .setMaxSendFragment()
  • .getPeerCertificate()
  • .getCertificate()
  • .enableTrace()
  • .disableRenegotiation()
  • .getCipher()
  • .getEphemeralKeyInfo()
  • .getTLSTicket()
  • .getSession()
  • .setSession()

base64url hashes are no longer data: urls

Previously, Bun would prepend data:base64, to the output of crypto.createHash("sha256").digest("base64url"). This is not what Node.js does, and it was causing issues with libraries that expected the output to be the same string as Node.js.

crypto.createHash("sha256").update("abc").digest("base64url");

//        Node.js:  "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
//     Bun v0.7.0:  "ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0"
// <= Bun v0.6.14:  "data:base64,ungWv48Bz-pBQUDeXa4iI7ADYaOWF3qctBD_YfIAFa0="

Terminal dimensions with process.stdout.columns and process.stdout.rows

process.stdout and process.stderr now support reading the terminal window's dimensions.

const { columns, rows } = process.stdout;
const [columns, rows] = process.stdout.getWindowSize();
const { columns, rows } = process.stderr;
const [columns, rows] = process.stderr.getWindowSize();

You can also use process.stdout.getWindowSize() if you want both dimensions at once.

Bugfixes

#3656 A memory leak in await new Response(latin1String).arrayBuffer() and await Response.json(obj).json() has been fixed.

After:

cpu: Apple M1 Max
runtime: bun 0.7.0 (arm64-darwin)

benchmark                                                        time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------------------------------------------------- -----------------------------
new Response().arrayBuffer() (new string each call, latin1)    12.9 µs/iter      (625 ns … 4.18 ms)      1 µs 567.17 µs 711.79 µs
new Response().arrayBuffer() (new string each call, utf16)    12.85 µs/iter     (1.67 µs … 1.56 ms)   2.17 µs 462.75 µs 621.13 µs
new Response().arrayBuffer() (existing string, latin1)         6.53 µs/iter     (6.21 µs … 7.07 µs)   6.64 µs   7.07 µs   7.07 µs

Peak memory usage: 49 MB

Before:

cpu: Apple M1 Max
runtime: bun 0.7.0 (arm64-darwin)

benchmark                                                        time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------------------------------------------------- -----------------------------
new Response().arrayBuffer() (new string each call, latin1)   13.51 µs/iter       (541 ns … 3.2 ms)   1.92 µs 553.42 µs 709.92 µs
new Response().arrayBuffer() (new string each call, utf16)    13.07 µs/iter     (1.71 µs … 3.43 ms)   2.13 µs 451.21 µs 651.67 µs
new Response().arrayBuffer() (existing string, latin1)         6.25 µs/iter     (5.79 µs … 6.81 µs)    6.4 µs   6.81 µs   6.81 µs

Peak memory usage: 292 MB

#3659 A module resolution bug causing the graphql package to import both CommonJS and ESM versions of the same modules has been fixed. This was fixed by aligning the package.json main field order closer to what Node.js does.

error: Cannot use GraphQLScalarType "String" from another module or realm.

Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.

https://yarnpkg.com/en/docs/selective-version-resolutions

Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.

#3663 A bug in bun:test lifecycle hooks caused beforeAll and afterAll to not run when no tests were defined in a scope. This has been fixed.

#3670 A bug when .env pointed to a directory caused Bun to crash. This has been fixed.

#3682 A TypeScript parser bug related to ternaries with spread operators has been fixed

Changelog

#3253feat(bun/test): Implement "bail" option for "bun test" by @TiranexDev
#3608Improve our internal typedefs by @paperdave
#3257Improvements to WebSocket and ServerWebSocket by @Electroid
#3630$npm_lifecycle_event should have the value of the last call by @TiranexDev
#3631Update docs/types for process by @colinhacks
#3637structured clone by @dylan-conway
#3650docs: add one missing line in typescript.md by @capaj
#3643Fixes #3641 by @Jarred-Sumner
#3614Support napi_wrap in constructors by @Jarred-Sumner
#3645Implement Workers by @Jarred-Sumner
#3654Fixes base64url encoding for crypto by @Jarred-Sumner
#365520% faster deserialize for structuredClone / postMessage with objects by @Jarred-Sumner
#3626workaround readable-stream compatibility by @alexlamsl
#3662[install] handle duplicated workspace declarations gracefully by @alexlamsl
#3664package json main field extension order by @dylan-conway
#3596[tls] General compatibility improvements by @cirospaciari
#3667zig upgrade by @dylan-conway
#3671fix(tls) patch checkServerIdentity by @cirospaciari
#3672feature(constants) add constants/node:constants module and tests(prisma) use prima 5.0.0 + use same connection for postgres, add prisma mssql (disabled for now) by @cirospaciari
#3678Better error for workspace dependency not found by @Jarred-Sumner
#3683move constants module to cpp by @cirospaciari
#3687fix #3682 by @dylan-conway
#3680fix createDecipheriv by @cirospaciari
#3688update root certificates and add tls.rootCertificates by @cirospaciari
#3089Implement AsyncLocalStorage by @paperdave
#3693Fix browser bundled string_decoder by @paperdave
#3694Fix vite by @paperdave
#3698Fixes #3670 by @Jarred-Sumner
#3697Support streams in response.formData() & request.formData, introduce Bun.readableStreamToFormData() by @Jarred-Sumner
#3706Improve types for FFI number types by @colinhacks
#3707fix start delay on Worker by @cirospaciari
#3709set _preload_modules to empty array by @dylan-conway
#3708fix 3702 by @dylan-conway
#3692Pass constructor arguments to TextDecoder by @Parzival-3141
#3711Fix builtins generator $lazy by @paperdave
#3710fix directory caching with workaround by @paperdave
#3713Fix builtins again by @paperdave
#3714fix process.exit status code handling by @paperdave
#3715fix isFIFO by @dylan-conway
#3717string escape edgecase by @dylan-conway