Bun

Bun v1.0.23


Jarred Sumner · January 16, 2024

Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager — all in one. In case you missed it, here are some of the recent changes to Bun.

This release fixes 40 bugs (addressing 194 👍 reactions). import & embed sqlite databases in Bun, Resource Management ('using' TC39 stage3) support, bundler improvements when building for Node.js, bugfix for peer dependency resolution, semver bugfix, 4% faster TCP on linux, Node.js compatibility improvements and more.

Previous releases

  • v1.0.22 fixes 29 bugs (addressing 118 👍 reactions), fixes bun install issues on Vercel, adds performance.mark() APIs, adds child_process support for extra pipes, makes Buffer.concat faster, adds toBeEmptyObject and toContainKeys matchers, fixes console.table width using emojis, and support for argv and execArgv options in worker_threads, and supports Brotli in fetch.
  • v1.0.21 - Fixes 33 bugs (addressing 80 👍 reactions). console.table() support. Bun.write, Bun.file, and bun:sqlite use less memory. Large file uploads with FormData use less memory. bun:sqlite error messages get more detailed. Memory leak in errors from node:fs fixed. Node.js compatibility improvements, and many crashes fixed.
  • v1.0.20 - Reduces memory usage in fs.readlink, fs.readFile, fs.writeFile, fs.stat and HTMLRewriter. Fixes a regression where setTimeout caused high CPU usage on Linux. HTMLRewriter.transform now supports strings and ArrayBuffer. fs.writeFile() and fs.readFile() now support hex & base64 encodings. Bun.spawn shows how much CPU & memory the process used.

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

Import sqlite databases in Bun

You can now import sqlite databases in Bun. This makes it simpler to get started with using sqlite in your project.

import db from './my.db' with {type: "sqlite"};

const {id} = db
  .query("SELECT id FROM users LIMIT 1")
  .get();

console.log(id); // 1

This also works with bun build --compile, which is great for deploying small databases to production. It means you can build your whole app into a single executable, and then deploy that executable alongside your database file, and use a different database file in develpoment versus production.

bun build --compile ./my-app.ts
# => ./my-app
# On your remote machine:
./my-app

Internally, this is roughly equivalent to:

import { Database } from "bun:sqlite";
const db = new Database("./my.db");

Embed sqlite databases into single-file executables

If your application would benefit from embedding into the executable itself, that is also supported. To embed a sqlite database, pass embed: "true" in the import attributes.

import db from './my.db'
          with {
            type: "sqlite",
            // Embed the database into the executable
            embed: "true"
          };

const {id} = db
  .query("SELECT id FROM users LIMIT 1")
  .get();

console.log(id); // 1

And now you can use bun build --compile to build your app into a single executable, and the database will be embedded into the executable itself.

bun build --compile ./my-app.ts
mv my.db /tmp/my.db # This isn't used in my-app anymore
./my-app # Since the database is embedded, it will work without the database file

You can also use embedded sqlite databases with bun build --target=bun. In that case it will copy the database file into the output directory, and import it from there.

bun build --target=bun ./my-app.ts --outdir=out
./out/my-app # Since the database is copied into the output directory, it will work without the database file

Upgraded SQLite to v3.45.0

SQLite 3.45.0 added JSONB support, which makes storing & reading JSON data faster. We've upgraded Bun (on Linux) to use this version of SQLite.

Embed .node files with bun build --compile

You can now embed NAPI (n-api) addons .node files with bun build --compile. This is useful for bundling native Node.js modules, like @anpi-rs/canvas.

canvas.ts
build
canvas.ts
import { promises } from "fs";
import { join } from "path";
import { createCanvas } from "@napi-rs/canvas";

const canvas = createCanvas(300, 320);
const ctx = canvas.getContext("2d");

ctx.lineWidth = 10;
ctx.strokeStyle = "#03a9f4";
ctx.fillStyle = "#03a9f4";

// Wall
ctx.strokeRect(75, 140, 150, 110);

// Door
ctx.fillRect(130, 190, 40, 60);

// Roof
ctx.beginPath();
ctx.moveTo(50, 140);
ctx.lineTo(150, 60);
ctx.lineTo(250, 140);
ctx.closePath();
ctx.stroke();

const pngData = await canvas.encode("png"); // JPEG, AVIF and WebP are also supported

await promises.writeFile(join(__dirname, "simple.png"), pngData);
build
bun build --compile canvas.ts

Then you can run the executable:

rm -rf node_modules # Not needed anymore
./canvas # => simple.png

Bugfixes for bun build --target=node

We've fixed a number of bugs in bun build --target=node.

Requiring Node.js builtin modules like fs and path is now supported.

var { promises } = require("fs");
var { join } = require("path");

promises.readFile(join(__dirname, "data.txt"));

Previously, this code would fail at runtime:

bun build --target=node ./my-app.ts --outfile=app.mjs
node ./app.mjs
TypeError: (intermediate value).require is not a function
    at __require (file:///app.mjs:2:22)
    at file:///app.mjs:7:20
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:28:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)

Now it works:

bun build --target=node ./my-app.ts --outfile=app.mjs
node ./app.mjs

A number of other bugs were fixed, including:

  • Missing dead-code elimination for __require function

Resource Management is now supported

We've implemented bundler support for Resource Management which is currently at TC39 stage 3 and available in TypeScript. This is a new feature that lets you manage resources like file handles, database connections, and network sockets. It is similar to the using keyword in C#.

// in an async function:
async function * g() {
  await using handle = acquireFileHandle(); // async-block-scoped critical resource
} // async cleanup

// in a block in an async context:
{
  await using obj = g(); // block-scoped declaration
  const r = await obj.next();
} // calls finally blocks in `g` and awaits result

You can use this at runtime in Bun and with bun build. We've implemented a polyfill that will work in Bun, in Node.js, and in web browsers (based on esbuild's polyfill).

We've also implemented partial support in JavaScriptCore, including Symbol.dispose, Symbol.asyncDispose, and SuppressedError. We have not implemented parser or AST support in JavaScriptCore, since we can use the transpiler for that for now.

Thanks to @paperdave for implementing this.

bun remove missing-pkg no longer errors

Previously, bun remove missing-pkg would error if the package was not installed. Now it does not error. This aligns the behavior with npm.

BunNPM
image
image

Thanks to @kaioduarte for this contribution.

4% faster TCP sockets on Linux

On Linux, sending large amounts of data over TCP sockets is now 4% faster. We've reduced the number of system calls involved in ticking the event loop.

Before (with some numbers removed for brevity):

timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 2
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
sendto(fd: 15, buff: 0x7f9093666dc0, len: 1016697408, flags: NOSIGNAL) = 1571592
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288) = 524288
timerfd_settime(ufd: 8, utmr: 0x456)   = 0
epoll_ctl(epfd: 7, op: ADD, fd: 8, event: 0x567) = -1 EEXIST (File exists)
epoll_wait(epfd: 7, events: 0x567, maxevents: 1024, timeout: 4294967295) = 2

After (with some numbers removed for brevity):

epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 127574
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
sendto(fd: 15, buff: 0x456, len: 72939772, flags: DONTWAIT|NOSIGNAL) = 4321878
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 127574
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
sendto(fd: 15, buff: 0x456, len: 68617894, flags: DONTWAIT|NOSIGNAL) = 4321878
epoll_wait(epfd: 7, events: 0x559c3cae78a0, maxevents: 1024, timeout: 4294967295) = 1
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288
recvfrom(fd: 14, ubuf: 0x123, size: 524288, flags: DONTWAIT) = 524288

Raised HTTP server header limit from 50 to 100

We've upgraded our uWebSockets fork to latest, and with that came a change to the default HTTP server header limit. It was raised from 50 to 100.

import.meta.dirname and import.meta.filename support

import.meta.dirname and import.meta.filename are now supported. They are aliases to import.meta.dir and import.meta.path. This is useful for compatibility with Node.js which released support for these very recently.

// /path/to/file.js

console.log(import.meta.dirname); // /path/to
console.log(import.meta.filename); // /path/to/file.js

FileHandle support in fs/promises

Previously, Bun's fs/promises did not support FileHandle objects. Now it does.

import { promises } from "node:fs";
const filehandle = await promises.open("file.txt", "r");
const stat = await filehandle.stat();
await filehandle.close();

events.on has been implemented

events.on returns an AsyncIterator that lets you loop over events as they happen.

Some packages rely on this API, and now it is supported in Bun.

import { on, EventEmitter } from "node:events";

const ee = new EventEmitter();

// Emit later on
process.nextTick(() => {
  ee.emit("foo", "bar");
  ee.emit("foo", 42);
});

for await (const event of on(ee, "foo")) {
  // The execution of this inner block is synchronous and it
  // processes one event at a time (even with await). Do not use
  // if concurrent execution is required.
  console.log(event); // prints ['bar'] [42]
}
// Unreachable here

Thanks to @nektro for implementing this.

process.binding('tty_wrap') support

process.binding('tty_wrap') is now supported. Some packages like readline-sync rely on this API, and even though bun's tty implementation is different from Node.js, we have implemented this Node.js internal binding API to support packages that depend on it.

process.binding("tty_wrap").TTY;

Implemented fs.fdatasync

fs.fdatasync is now implemented, thanks to @nektro.

Fixed: 'Not a string or buffer' from zlibBufferSync error

Our polyfill of zlibBufferSync was not properly checking for non-Buffers, like Uint8Array. This has been fixed, thanks to @Electroid.

Fixed: File descriptor leak in Bun.spawn() with IPC

When IPC was in use, Bun.spawn() would neglect to close the 2nd socket's file descriptor, causing a file descriptor leak. This has been fixed.

Fixed: handle missing .host in node:url consistently with node

The following snippet would behave differently in Bun than in Node.js:

const url = require("url");

console.log(url.parse("http://"));

This has been fixed, thanks to @kaioduarte.

Bumped Node.js version

The version of Node.js reported in process.versions.node has been bumped to v21.6.0 which is the latest version at the time of release.

Fixed: peer dependency upgrade resolution

Let's say you installed drizzle-cli and drizzle-orm. drizzle-cli had a peer dependency on drizzle-orm@^1.0.0. You upgrade drizzle-orm to 1.0.1, you expect drizzle-cli to now use drizzle-orm@1.0.1. Previously, this would not happen -- but now it does, thanks to @eriklangille.

Fixed: semver comparison bug

The following test would behave differently when compared with node-semver versus Bun.semver:

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

test("semver with multiple tags work properly", () => {
  expect(semver.satisfies("3.4.5", ">=3.3.0-beta.1 <3.4.0-beta.3")).toBeFalse();
});

This bug impacted bun install and Bun.semver.

This has been fixed, thanks to @Electroid.

Fixed: WebSocket on connection failure threw an error

The following code would throw an error in Bun, but not in web browsers:

const ws = new WebSocket("wss://apsodkapsodkpo.com:3000");
// => Uncaught Error:

Instead, it's supposed to emit an error event. This has been fixed, thanks to @LukasKastern.

Fixed: Invalid stdio option "null"

The following code would behave differently in Bun vs Node.js:

import cp from "child_process";
cp.spawnSync("ls", ["-lagh"], { stdio: [null, "inherit", null] });

This has been fixed, Thanks to @Electroid.

Fixed: printed exception twice in Bun.serve() sometimes

A bug where the following code would print the exception twice has been fixed:

Bun.serve({
  port: 3000,
  fetch: async () => {
    await Bun.sleep(1);
    throw new Error("A");

    return new Response("A");
  },
  error() {
    return new Response("Handled");
  },
});

This has been fixed, thanks to @LukasKastern.

Fixed: ServerWebSocket idleTimeout

An event loop bug was fixed that could cause ServerWebSocket timeout to take much longer than expected.

Thanks to @LukasKastern for fixing this.

Fixed: macOS large file uploads sometimes failed

A bug impacting macOS where the final chunk of a large file upload (> 512 KB) received all at once would sometimes lose part of the final chunk has been fixed. The bug was caused by incorrect handling of KQueue events. Unlike Linux (epoll), kqueue may send both the socket hangup and the readable event at the same time, and we were not handling that case correctly when the data was larger than the read buffer.

Thanks to 12 contributors who made this release possible!

Full Changelog: https://github.com/oven-sh/bun/compare/bun-v1.0.22...bun-v1.0.23