Bun v0.3.0

Ashcon Partovi · December 7, 2022

Today, Bun has two main priorities: stability and compatibility with Node.js and Web APIs. In v0.3.0, we've made significant progress toward both of these goals. There are a lot of changes to cover, so we'll start with the highlights.

Note — We're a small team working to make building with JavaScript faster and simpler. If you're interested in joining us, check out our careers page, we're hiring JavaScript, C/C++, and Zig engineers!

To install:

curl -fsSL https://bun.sh/install | bash

To upgrade:

bun upgrade

Stability improvements

  • Bun now uses 3-5x less memory under load. Previously, Bun wasn't scheduling garbage collection in coordination with the event loop. (See the benchmark here)

  • Bun now has better formatting for console.log() (important for debugging!)

    Better formatting for console.log()

  • Bun has fixed several bugs with text encoding and now uses simdutf for 3x faster TextEncoder.encodeInto()

    Bun now has 3x faster text encoding

  • Bun now works in more Linux environments, including Amazon Linux 2 and builds for Vercel and Cloudflare Pages. (Previously, you might have seen errors like: "version 'GLIBC_2.29' not found")

There were also many other changes that:

Node.js compatibility

Bun now supports the following Node.js APIs:

New and improved APIs

At a glance, here's what changed:

console is now an AsyncIterable

Bun is making it easier to read input from your console with APIs that make sense for non-browsers. console is now an AsyncIterable which allows you to stream lines of text from stdin by for await-looping over console.

for await (const line of console) {
  console.log("Received:", line);

To write to stdout, you can also use console.write(), which skips the formatting of console.log().

console.write("hello", "world", "\n");

const response = await fetch("https://example.com/");
console.write("Response: ", await response.arrayBuffer());

Automatic package installs from npm

When there is no node_modules directory, Bun will now automatically install packages from npm on import. To save disk space, packages are downloaded into the shared global cache directory. For security, postinstall scripts are not run.


To specify a package version, you can either continue to use package.json or you can include a range specifier in the import path. These range specifiers are only supported when there is no package.json present.

import { renderToReadableStream } from "react-dom@latest/server";
import { serve } from "bun";

  async fetch(req) {
    const stream = await renderToReadableStream(
          <h1>Hello, world!</h1>
    return new Response(stream);

Bun continues to support the node_modules folder, which means this isn't a breaking change. If you want to disable this feature, you can run Bun using the --no-install flag.


Bun now exposes FileSystemRouter, a fast API for resolving incoming paths against a file-system router. Once initialized, it can rapidly resolve paths, or a Request, against the router.

Consider the following Next.js-style pages directory:

├── server.ts
├── pages
  ├── index.tsx
  ├── settings.tsx
  ├── blog
  │   ├── index.tsx
  │   └── [slug].tsx
  └── [[...catchall]].tsx

In server.ts, you can initialize a FileSystemRouter that points to your pages directory.

import { FileSystemRouter } from "bun";

const router = new FileSystemRouter({
  dir: import.meta.dir + "/pages",
  style: "nextjs",

Router styles — Currently, the only supported style is nextjs. We plan to support others in the future, including the Next.js 13-style app directory.

You can use the router instance to resolve incoming requests against your defined pages. This matching is implemented in native code and is much faster than a JavaScript-based router.

// { filePath: "/pages/index.tsx" }

// { filePath: "/pages/blog/index.tsx" }

// {
//   filePath: "/pages/blog/[slug].tsx" }
//   params: { slug: "hello-world" },
//   query: { foo: "bar" }
// }

Expect matchers in bun:test

More methods on expect() matchers have been implemented in bun:test. We've also made performance improvements to expect() which makes toEqual() 100x faster than Jest. Longer-term, we want bun:test to be an incredibly fast drop-in replacement for companies using Jest & Vitest.

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

test("new expect() matchers", () => {
  expect({ a: 1 }).toEqual({ a: 1, b: undefined });
  expect({ a: 1 }).toStrictEqual({ a: 1 });
  expect(new Set()).toHaveProperty("size");

Note — You can try out the test runner with bun wiptest.

New methods on Headers

The Headers class now implements the .getAll() and .toJSON() methods. These are both technically non-standard methods, but we think it will make your life easier.

const headers = new Headers();
headers.append("Set-Cookie", "a=1");
headers.append("Set-Cookie", "b=1; Secure");

console.log(headers.getAll("Set-Cookie")); // ["a=1", "b=1; Secure"]
console.log(headers.toJSON()); // { "set-cookie": "a=1, b=1; Secure" }

Resizable ArrayBuffer and growable SharedArrayBuffer

The ability to .resize() an ArrayBuffer and .grow() a SharedArrayBuffer has been implemented in WebKit and is now available in Bun. Thanks, WebKit!

const buffer = new ArrayBuffer({
  byteLength: 1024,
  maxByteLength: 2048,

console.log(buffer.byteLength); // 1024
console.log(buffer.byteLength); // 2048


The Array.fromAsync() method has also been implemented in WebKit and is now available in Bun. It is similar to Array.from() except it accepts async-iterable inputs. Thanks again, WebKit!

async function* listReleases() {
  for (let page = 1; ; page++) {
    const response = await fetch(
    const releases = await response.json();
    if (!releases.length) {
    for (const release of releases) {
      yield release;

const releases = await Array.fromAsync(listReleases());


There were a lot of other changes! Here's the complete list: