Bun

Bun v1.1.31


Ashcon Partovi · October 18, 2024

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

This release fixes 41 bugs (addressing 595 👍). It includes node:http2 server and gRPC server support, ca and cafile support in bun install, Bun.inspect.table, bun build --drop, Promise.try, Buffer.copyBytesFrom, iterable SQLite queries, iterator helpers, and several Node.js compatibility improvements and bugfixes.

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

Support for node:http2 server and gRPC

In this release of Bun, we added support for our most upvoted feature request: HTTP2 server and gRPC support. Bun has supported the node:http2 client since v1.0.13, but we didn't support the server, until now.

In Bun, node:http2 runs 2.4x faster than in Node v23.

You can use the node:http2 API to create an HTTP2 server.

import { createSecureServer } from "node:http2";
import { readFileSync } from "node:fs";

const server = createSecureServer({
  key: readFileSync("privkey.pem"),
  cert: readFileSync("cert.pem"),
});

server.on("error", (error) => console.error(error));
server.on("stream", (stream, headers) => {
  stream.respond({
    ":status": 200,
    "content-type": "text/html; charset=utf-8",
  });
  stream.end("<h1>Hello from Bun!</h1>");
});

server.listen(3000);

With HTTP2 support, you can also use gRPC with packages like @grpc/grpc-js.

const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDefinition = protoLoader.loadSync("benchmark.proto", {});
const proto = grpc.loadPackageDefinition(packageDefinition).benchmark;
const fs = require("fs");

function ping(call, callback) {
  callback(null, { message: "Hello, World" });
}

function main() {
  const server = new grpc.Server();
  server.addService(proto.BenchmarkService.service, { ping: ping });
  const tls =
    !!process.env.TLS &&
    (process.env.TLS === "1" || process.env.TLS === "true");
  const port = process.env.PORT || 50051;
  const host = process.env.HOST || "localhost";
  let credentials;
  if (tls) {
    const ca = fs.readFileSync("./cert.pem");
    const key = fs.readFileSync("./key.pem");
    const cert = fs.readFileSync("./cert.pem");
    credentials = grpc.ServerCredentials.createSsl(ca, [
      { private_key: key, cert_chain: cert },
    ]);
  } else {
    credentials = grpc.ServerCredentials.createInsecure();
  }
  server.bindAsync(`${host}:${port}`, credentials, () => {
    console.log(
      `Server running at ${tls ? "https" : "http"}://${host}:${port}`,
    );
  });
}

main();

CA support in bun install

You can now configure CA certificates for bun install. This is useful when you need to install packages from your company's private registry, or if you want to use self-signed certificate.

bunfig.toml
[install]
# The CA certificate as a string
ca = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# A path to a CA certificate file. The file can contain multiple certificates.
cafile = "path/to/cafile"

If you don't want to change your bunfig.toml file, you can also use the --ca and --cafile flags.

bun install --cafile=/path/to/cafile
bun install --ca="..."

If you are using an existing .npmrc file, you can also configure CA certificates there.

.npmrc
cafile=/path/to/cafile
ca="..."

bun build --drop

You can use --drop to remove function calls from your JavaScript bundle. This is useful if you want to remove debug code from your production bundle. For example, if you pass --drop=console, all calls to console.log will be removed from your code.

JavaScript
CLI
JavaScript
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  drop: ["console", "anyIdentifier.or.propertyAccess"],
})
CLI
bun build ./index.tsx --outdir ./out --drop=console --drop=anyIdentifier.or.propertyAccess

bun --drop

--drop also works at runtime.

index.ts
for (let i = 0; i < 3; i++) {
  console.log("hello called!");
}

Before --drop, you'd see hello called! 3 times.

bun ./index.ts
hello called!
hello called!
hello called!

With --drop=console, console.log calls get dropped and you don't see hello called! anymore.

bun --drop=console ./index.ts
# no output

Bun.inspect.table()

You can now use Bun.inspect.table to format tabular data into a string. It is similar to console.table, except it returns a string rather than printing to the console.

console.log(
  Bun.inspect.table([
    { a: 1, b: 2, c: 3 },
    { a: 4, b: 5, c: 6 },
    { a: 7, b: 8, c: 9 },
  ]),
);

// ┌───┬───┬───┬───┐
// │   │ a │ b │ c │
// ├───┼───┼───┼───┤
// │ 0 │ 1 │ 2 │ 3 │
// │ 1 │ 4 │ 5 │ 6 │
// │ 2 │ 7 │ 8 │ 9 │
// └───┴───┴───┴───┘

You can also pass an array of property names to display only a subset of properties.

console.log(
  Bun.inspect.table(
    [
      { a: 1, b: 2, c: 3 },
      { a: 4, b: 5, c: 6 },
    ],
    ["a", "c"],
  ),
);

// ┌───┬───┬───┐
// │   │ a │ c │
// ├───┼───┼───┤
// │ 0 │ 1 │ 3 │
// │ 1 │ 4 │ 6 │
// └───┴───┴───┘

If you want to enable ANSI colors, you can set the colors property on the options object.

console.log(
  Bun.inspect.table(
    [
      { a: 1, b: 2, c: 3 },
      { a: 4, b: 5, c: 6 },
    ],
    {
      colors: true,
    },
  ),
);

Iterable SQLite queries

Bun has a built-in SQLite API, that makes it easy to query SQLite databases. Instead of returning an array of rows, you can now return an iterator that yields rows as they are returned from the database.

import { Database } from "bun:sqlite";

const sst = new Database(":memory:");
sst.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)");

class User {
  id: number;
  name: string;
  email: string;

  get nameAndEmail() {
    return `${this.name} <${this.email}>`;
  }
}

const rows = sst
  .query("SELECT * FROM users")
  .as(User)
  .iterate() // <--- here
  // call the .nameAndEmail property on each row
  .map((row) => row.nameAndEmail);

for (const row of rows) {
  console.log(row);
}

// prints nothing because sst has no users

You can also call the .iterate() method on a query to get an iterator that yields rows as they are returned from the database.

for (const row of db.query("SELECT * FROM users").iterate()) {
  console.log(row); // { id: 1, name: "Bun" }
}

Thanks to @Skywalker13 for implementing this!

Iterator helpers

In this release of Bun, there are new APIs that make it easier to work with JavaScript iterators and generators. These APIs were introduced in a TC39 proposal, and are now available in Safari and Chrome. Thanks to the WebKit team for implementing these APIs, especially @sosukesuzuki and @shvaikalesh!

iterator.map(fn)

Returns an iterator that yields the results of the fn function applied to each value of the original iterator, similar to Array.prototype.map.

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(3, 5).map((x) => x * 2);
result.next(); // { value: 6, done: false }

iterator.flatMap(fn)

Returns an iterator that yields the values of the original iterator, but flattens the results of the fn function, similar to Array.prototype.flatMap.

function* randomThoughts(): Generator<string> {
  yield "Bun is written in Zig";
  yield "Bun runs JavaScript and TypeScript";
}

const result = randomThoughts().flatMap((x) => x.split(" "));
result.next(); // { value: "Bun", done: false }
result.next(); // { value: "is", done: false }
// ...
result.next(); // { value: "TypeScript", done: false }

iterator.filter(fn)

Returns an iterator that only yields values that pass the fn predicate, similar to Array.prototype.filter.

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(3, 5).filter((x) => x % 2 === 0);
result.next(); // { value: 4, done: false }

iterator.take(n)

Returns an iterator that yields the first n values of the original iterator.

function* odds(): Generator<number> {
  let i = 1;
  while (true) {
    yield i;
    i += 2;
  }
}

const result = odds().take(1);
result.next(); // { value: 1, done: false }
result.next(); // { done: true }

iterator.drop(n)

Returns an iterator that yields all values of the original iterator, except the first n values.

function* evens(): Generator<number> {
  let i = 0;
  while (true) {
    yield i;
    i += 2;
  }
}

const result = evens().drop(2);
result.next(); // { value: 4, done: false }
result.next(); // { value: 6, done: false }

iterator.reduce(fn, initialValue)

Reduces the values of an iterator with a function, similar to Array.prototype.reduce.

function* powersOfTwo(): Generator<number> {
  let i = 1;
  while (true) {
    yield i;
    i *= 2;
  }
}

const result = powersOfTwo()
  .take(5)
  .reduce((acc, x) => acc + x, 0);
console.log(result); // 15

iterator.toArray()

Returns an array that contains all the values of the original iterator. Make sure that the iterator is finite, otherwise this will cause an infinite loop.

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(1, 5).toArray();
console.log(result); // [1, 2, 3, 4]

iterator.forEach(fn)

Calls the fn function on each value of the original iterator, similar to Array.prototype.forEach.

function* randomThoughts(): Generator<string> {
  yield "Bun is written in Zig";
  yield "Bun runs JavaScript and TypeScript";
}

const result = randomThoughts().forEach((x) => console.log(x));
// Bun is written in Zig
// Bun runs JavaScript and TypeScript

iterator.find(fn)

Returns the first value of the original iterator that passes the fn predicate, similar to Array.prototype.find. If no such value exists, it returns undefined.

function* range(start: number, end: number): Generator<number> {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const result = range(0, 99).find((x) => x % 100 === 0);
console.log(result); // undefined

Promise.try

The Promise.try method is similar to Promise.resolve, but it also works with synchronous functions.

Promise.try(() => {
  return 1 + 1;
}).then((result) => {
  console.log(result); // 2
});

This is a stage4 TC39 proposal (i.e. it has graduated from proposal to standardized ECMAScript). Thanks to @rkirsling for implementing this!

9x faster Error.captureStackTrace

expect(fn).toThrow() prints the return value if it does not throw

Before:

image

And now:

image

Thanks to @nektro for implementing this!

Warn on first idle request timeout

New error: Mismatched JSX closing tag

Previously, the following code would not throw an error, even though it's invalid JSX:

const Oopsie = () => {
  return (
    <h1>
      That should be an h1! Not a div.
    </div>
  );
};

We should have always thrown an error here, but we were not. Now we do:

❯ bun build Oopsie.tsx
5 |     </div>
          ^
error: Expected closing JSX tag to match opening tag "<h1>"
    at Oopsie.tsx:5:7

3 |     <h1>
         ^
note: Opening tag here:
   at Oopsie.tsx:3:6

Thanks to @DonIsaac for fixing this!

Node.js compatibility

New: Buffer.copyBytesFrom(view[, offset[, length]])

You can now use the Buffer.copyBytesFrom method to create a new Buffer by copying bytes from a typed array or DataView.

const u16 = new Uint16Array([0, 0xffff]);
const buf = Buffer.copyBytesFrom(u16, 1, 1);
u16[1] = 0;
console.log(buf.length); // 2
console.log(buf[0]); // 255
console.log(buf[1]); // 255

Thanks to @nektro for implementing this!

Fixed: fs.open()

We fixed various issues in fs.open(), so it more closely matches Node.js's behavior. Thanks to @nektro for fixing this!

Fixed: Node-API bugs

We've been working on fixing bugs in Bun's implementation of Node-API. Thanks to @190n for fixing these bugs!

  • napi_create_empty_array does not crash when the array is too large
  • napi_create_empty_array uses empty slots, instead of undefined values
  • napi_get_value_int32 uses the same bitwise conversions as in JavaScript
  • napi_get_value_int64 now matches Node.js's behavior
  • napi_throw_error and napi_create_error are now tested and don't sometimes crash

Fixed: Setting UV_THREADPOOL_SIZE

There are some Node.js packages that set the UV_THREADPOOL_SIZE environment variable to control the number of threads used by libuv. While Bun only uses libuv on Windows, and only for some APIs, it will now respect this environment variable.

Fixed: Buffer.from JSON serialization

When you call JSON.stringify on a Buffer, it adds {type: "Buffer", value: [...buffer contents]} to the JSON. This already worked, but Buffer.from would not accept this JSON. Now it does.

const buf = Buffer.from([1, 2, 3]);
JSON.stringify(buf); // "{"type":"Buffer","data":[1,2,3]}"
Buffer.from(JSON.parse('{"type":"Buffer","data":[1,2,3]}'))[0] === 1; // works

Previously, this would throw:

TypeError: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.
      at [file]:3:20

Thanks to @nektro for fixing this!

Fixed: node:child_process.SpawnResult .stderr should be null if .stdio is not "pipe"

import child_process from "node:child_process";

const res = child_process.spawn("ls", {
  stdio: "inherit",
});
console.log(res.stderr); // now 'null'

This unblocked use of the tinyexec package.

Thanks to @nektro for fixing this!

Bugs squashed

Fixed: Format of console.table with numeric keys

We fixed a bug where console.table would format with incorrect spacing when the keys were numeric. Thanks to @pfgithub for fixing this!

console.table({test: {"10": 123, "100": 154}});

// Before
┌──────┬─────┬─────┐
│      │ 10100
├──────┼─────┼─────┤
│ test │     │     │
└──────┴─────┴─────┘

// After
┌─────────┬─────┬─────┐
│ (index) │ 10100
├─────────┼─────┼─────┤
│ test    │ 123154
└─────────┴─────┴─────┘

Fixed: Crash using Bun.color

In a recent release, we introduced Bun.color, an API that allows you to parse and transform colors in different formats, including css, ansi, hex, rgb, hsl, and more.

We fixed a bug where Bun.color would crash if you did not pass it a format string.

Fixed: Regression with instanceof

An instanceof regression introduced in Bun v1.1.30 through a WebKit upgrade has been fixed. The bug could be caused by JavaScript code storing the output value of instanceof in the same variable as the input. The error message would look like:

error: [BUG] Unreachable
      at ... (input.js:2247:47)

This bug was most often seen when using sass with source maps enabled.

Fixed thanks to @hyjorc1!

Fixed: Transpiler bug with using and generators

We fixed a bug where a variable declaration before a using statement would cause the transpiler to hoist a generator outside the scope of the using declaration. This would cause a ReferenceError, but has now been fixed thanks to @snoglobe.

Fixed: File permissions with bun install

Some npm packages are published with incorrect file permissions, we added a check in Bun to ensure that files extracted from an npm package are at-least readable. This matches the behavior of npm install.

Fixed: Missing metadata publishing with bun publish

A bug was fixed where bun publish was sending publish requests without important package metadata, including dependencies and binaries from bin or directories.bin. This would cause published packages to install incorrectly due to missing transitive dependencies and binaries in node_modules/.bin.

Fixed: empty string argument for fs.mkdir

An integer overflow caused by passing an empty string to fs.mkdir has been fixed. This would only happen when the recursive option was set to true.

const fs = require("fs");
fs.mkdirSync("", { recursive: true }); // throws ENOENT

Fixed: fetch keepalive option

The keepalive option for fetch was previously ignored. Now it disables the "Connection: keep-alive" header from being sent by default.

const res = await fetch("https://example.com", { keepalive: false });
console.log(res.headers.get("connection")); // "close"

Thanks to 19 contributors!