Bun v1.0.15 fixes 23 bugs (addressing 117 👍 reactions), tsc starts 2x faster, prettier 40% faster. Stable WebSocket client, syntax-highlighted errors, cleaner stack traces, add custom test matchers with expect.extend() + additional expect matchers, TensorFlow.js support, and more.
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:
v1.0.12- Addsbun -efor evaluating scripts,bun --env-filefor loading environment variables,server.url,import.meta.env,expect.unreachable(), improved CLI help output, and morev1.0.13- Fixes 6 bugs (addressing 317 👍 reactions). 'http2' module & gRPC.js work now. Vite 5 & Rollup 4 work. Implements process.report.getReport(), improves support for ES5 'with' statements, fixes a regression in bun install, fixes a crash when printing exceptions, fixes a Bun.spawn bug, and fixes a peer dependencies bugv1.0.14-Bun.Glob, a fast API for matching files and strings using glob patterns. It also fixes a race condition when extracting dependencies duringbun install, improves TypeScript module resolution innode_modules, and makes error messages easier to read.
To install Bun:
curl -fsSL https://bun.sh/install | bashnpm install -g bunbrew tap oven-sh/bunbrew install bundocker pull oven/bundocker run --rm --init --ulimit memlock=-1:-1 oven/bunTo upgrade Bun:
bun upgradeTranspiler cache makes CLIs like tsc up to 2x faster
Bun lets you run TypeScript and JSX files without a build step. This works by running Bun's JavaScript transpiler before files load. In this release, we're introducing a content-addressable cache for files larger than 50KB, to avoid the performance overhead of transpiling the same files repeatedly. This makes CLIs, such as tsc, run up to 2x faster.
tsc --help gets a 2x speedup compared to Bun v1.0.14:
Benchmark 1: bun --bun ./node_modules/.bin/tsc --help
Time (mean ± σ): 82.2 ms ± 2.6 ms [User: 70.1 ms, System: 14.3 ms]
Range (min … max): 78.4 ms … 87.1 ms 37 runs
Benchmark 2: bun-1.0.14 --bun ./node_modules/.bin/tsc --help
Time (mean ± σ): 197.0 ms ± 3.6 ms [User: 172.0 ms, System: 27.2 ms]
Range (min … max): 192.4 ms … 204.4 ms 14 runs
Benchmark 3: node ./node_modules/.bin/tsc --help
Time (mean ± σ): 113.8 ms ± 3.2 ms [User: 103.6 ms, System: 16.0 ms]
Range (min … max): 110.0 ms … 123.4 ms 23 runs
Summary
bun --bun ./node_modules/.bin/tsc --help ran
1.38 ± 0.06 times faster than node ./node_modules/.bin/tsc --help
2.40 ± 0.09 times faster than bun-1.0.14 --bun ./node_modules/.bin/tsc --help
Prettier gets up to 40% faster (compared to Bun v1.0.14)
Benchmark 1: bun --bun ./node_modules/.bin/prettier --write ./examples/hashing.js
Time (mean ± σ): 124.5 ms ± 3.2 ms [User: 144.5 ms, System: 23.4 ms]
Range (min … max): 119.9 ms … 131.8 ms 23 runs
Benchmark 2: bun-1.0.14 --bun ./node_modules/.bin/prettier --write ./examples/hashing.js
Time (mean ± σ): 184.4 ms ± 4.1 ms [User: 202.7 ms, System: 28.6 ms]
Range (min … max): 175.2 ms … 192.9 ms 15 runs
Benchmark 3: node ./node_modules/.bin/prettier --write ./examples/hashing.js
Time (mean ± σ): 162.9 ms ± 3.7 ms [User: 161.8 ms, System: 42.5 ms]
Range (min … max): 158.2 ms … 170.7 ms 17 runs
Summary
bun --bun ./node_modules/.bin/prettier --write ./examples/hashing.js ran
1.31 ± 0.04 times faster than node ./node_modules/.bin/prettier --write ./examples/hashing.js
1.48 ± 0.05 times faster than bun-1.0.14 --bun ./node_modules/.bin/prettier --write ./examples/hashing.js
To minimize disk usage, the transpiler cache is global and shared across all projects. It's safe to delete the cache at any time, and since it's content-addressable it will not contain duplicate entries. If you are running Bun with an emphemeral filesystem, such as Docker, it is recommended to disable the cache.
If you want to customize the path where these files are cached, you can set the BUN_RUNTIME_TRANSPILER_CACHE_PATH environment variable. You can also set the value to 0 to disable the cache entirely.
Stable WebSocket client
This release includes a (finally) stable WebSocket client. Bun has supported WebSocket for a while, but it had various protocol bugs, such as disconnecting early or not handling message fragmentation properly. Now, it is stable and should work with most WebSocket servers.
const ws = new WebSocket("wss://echo.websocket.org/");
ws.addEventListener("message", ({ data }) => {
console.log("Received:", data);
});
ws.addEventListener("open", () => {
ws.send("Hello!");
});
To test compliance, we used the Autobahn test suite, which is the de-facto standard for testing WebSocket implementations. Bun passed 100% of Autobahn's tests, excluding those that test for compression. (our WebSocket client does not support compression yet, but will soon.)
That means all of these issues, and more, have now been fixed.
Thanks to @cirospaciari for fixing these bugs and getting Autobahn test compliance!
expect.extend() and more matchers
You can now defined custom test matchers using expect.extend(). This is useful when you want to create a custom matcher that is reusable across multiple tests. For example, you can create a custom matcher that checks if a number is within a range:
import { test, expect } from "bun:test";
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
if (pass) {
return {
message: () =>
`expected ${received} not to be within range ${floor} - ${ceiling}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${received} to be within range ${floor} - ${ceiling}`,
pass: false,
};
}
},
});
test("toBeWithinRange()", () => {
expect(1).toBeWithinRange(1, 99); // ✅
expect(100).toBeWithinRange(1, 99); // ❌ expected 100 to be within range 1 - 99
});
Bun also supports more asymmetric matchers, including:
In addition to the above asymetric matchers, Bun is introducing two new asymetric matchers that work for promises.
expect.resolvesToexpect.rejectsTo
For example, you can use expect.resolvedTo to check for a promise that resolves to a value:
import { test, expect } from "bun:test";
import { getTempurature, getForecast } from "./weather";
test("expect.resolvedTo", async () => {
const weather = {
tempurature: getTempurature(),
forecast: getForecast(),
};
await expect(weather).toMatchObject({
tempurature: expect.resolvedTo.closeTo(10, 5),
forecast: expect.resolvedTo.stringMatching(/rain|snow|sleet/),
});
});
Thanks to @otgerrogla for implementing these features and also fixing various bugs with expect().
There are also more mock matchers available, including:
expect().toHaveBeenCalledWith()expect().toHaveBeenLastCalledWith()expect().toHaveBeenNthCalledWith()
You can use these matchers to check if a function was called with certain arguments:
import { test, mock, expect } from "bun:test";
test("toHaveBeenCalledWith()", () => {
const fn = mock(() => {});
sum(1, 2, 3);
expect(fn).toHaveBeenCalledWith(1, 2, 3); // ✅
expect(fn).toHaveBeenCalledWith(1, 2); // ❌
});
Thanks to @james-elicx for implementing these matchers and making various error messages better.
Syntax-highlighting for errors
When an exception occurs in Bun, it prints a stack trace to the console with a multi-line source code preview. Now that source code preview gets syntax highlighted, which makes it easier to read.
The syntax highlighter also extends to all other errors that include source code, such as this one when using bun install with multiple workspace packages sharing the same name.
Thanks to @paperclover for working on this!
Better Error.stack traces
We've made several improvements to how Error.stack traces are formatted.
Stack traces now include less noise, such as internal functions that are not relevant to the error.
1 | throw new Error("Oops");
^
error: Oops
at /Users/jarred/Desktop/oops.js:1:7
- at globalThis (/Users/jarred/Desktop/oops.js:3:14)
- at overridableRequire (:1:20)
at /Users/jarred/Desktop/index.js:3:8
- at globalThis (/Users/jarred/Desktop/index.js:3:8)
We also fixed a bug where Bun would not detect if the Error.stack property was modified. This can happen if you use a library that modifies the stack trace, such as concatenating multiple traces together.
const err = new Error("Oops 1");
err.stack += "\n" + new Error("Oops 2").stack;
throw err;
Bun now re-parses error.stack, so that modified stack traces are properly detected and formatted.
recursive in fs.readdir() is 40x faster than Node.js
Bun now supports the recursive option in fs.readdir(), which is used to recursively read a directory.
import { readdir } from "fs/promises";
const results = await readdir(__dirname, { recursive: true });
console.log(results); // ["a.js", "b/c.js" ...
When running readdir() in the test/ directory of Bun's repository, on Linux it's 40x faster than Node.js v21.2.0.
Fixed.https://t.co/WhuVBhNdc3
— Jarred Sumner (@jarredsumner) November 24, 2023
CommonJS modules get 1% faster
We made some improvements to how Bun loads CommonJS modules, which makes them 1% faster.
This is because we simplified our internal wraper functions for CommonJS. Previosuly, our wrapper functions were written in JavaScript, with some help from Bun's transpiler. Now, this is done in native code, so it's faster and uses less memory.
Thanks to @paperclover for implementing this!
TensorFlow.js now works
There was an outstanding bug in Bun's implementation of napi_create_string_utf16 and napi_create_arraybuffer that prevented TensorFlow.js from working. This has now been fixed.
import * as tf from "@tensorflow/tfjs-node";
const model = tf.sequential({
layers: [
tf.layers.dense({ units: 128, activation: "relu", inputShape: [1] }),
tf.layers.dense({ units: 3 }),
tf.layers.softmax(),
],
});
model.compile({
optimizer: "adam",
loss: "categoricalCrossentropy",
metrics: ["categoricalAccuracy"],
});
const result = model.predict(tf.tensor([0]));
if (Array.isArray(result)) throw new Error("Expected a single tensor");
const prediction = await result.data();
console.log(prediction); // Float32Array(3) [...]
Support for crypto.sign and crypto.verify
Bun now supports sign and verify in the node:crypto module, which can be used to sign and verify data using a private key.
import { sign, generateKeyPairSync } from "node:crypto";
const { privateKey } = generateKeyPairSync("ed25519");
const signature = sign(undefined, Buffer.from("foo"), privateKey);
console.log(signature); // Buffer(64) [ ... ]
Thanks to @cirospaciari for implementing this feature!
bun install migration from package-lock.json v2
Bun is now able to migrate package-lock.json files that have a lockfileVersion of 2. This is useful if you are migrating from npm to Bun.
❯ cat package-lock.json | jq .lockfileVersion
2
❯ bun install
bun install v1.0.15
[3.57ms] migrated lockfile from package-lock.json
+ svelte@4.0.0 (v4.2.8 available)
21 packages installed [265.00ms]
bun install duplicate workspace bugfix
An error regarding duplicate workspace packages has been fixed, thanks to @dylan-conway.
Trailing commas with console.log
Bun now includes trailing commas for objects that are printed using console.log. This makes it easier to copy-paste the output from your console, do a search-and-replace, and matches what code formatters like prettier do.
console.log({ a: 1, b: 2, c: "3" });
{
a: 1,
b: 2,
- c: "3"
+ c: "3",
}
Thanks to @hustLer2k for implementing this improvement, and to @ArnaudBarre for the suggestion!
Implemented: console.timeLog
There was a bug that prevented console.timeLog from working. This was fixed thanks to @lqqyt2423.
Fixed: Detached usage of ReadableStream
There was a bug where a undefined is not an object error would throw when a ReadableStream was detached, which has now been fixed.
const { exited, stdout } = Bun.spawn(["echo", "hello"], {
stdout: "pipe",
});
await exited;
// Since the process has exited, the `stdout` ReadableStream is detached.
console.log(await Bun.readableStreamToText(stdout));
// Before: error: undefined is not an object
// After: "hello\n"
Fixed: fs.opendir() has path property
Previously, fs.opendir() did not have a path property, this has now been fixed thanks to @samfundev.
import { test, expect } from "bun:test";
import { opendir } from "fs/promises";
test("opendir() has `path` property", () => {
const result = await opendir(".");
expect(result).toHaveProperty("path", ".");
});
Fixed: Duplicate Content-Range header
There was a bug where Bun would send duplicate Content-Range headers.
const file = Bun.file("video.mp4");
const start = 0;
const end = 1000;
const range = file.slice(start, end);
return new Response(range, {
status: 206,
headers: {
"Content-Range": `bytes ${start}-${end}/${file.size}`,
},
});
Bun would not properly detect that a Content-Range header was already set, and would send a duplicate header.
content-range: bytes 0-1000/10000
- content-range: bytes 0-1000/*
This was fixed thanks to @libersoft-org.
Fixed: Various transpiler bugs
Hyphenated keys in tsconfig.json
There was a bug where if a tsconfig.json included a key that contained hyphens, parsing would fail. This was fixed thanks to @DontBreakAlex.
{
"key-with-hyphens": "value"
}
Spreading elements in JSX
There was a bug where spreading a child element into a parent element would cause an unexpected error. This was fixed thanks to @rhyzx.
Input:
const a = <h1>{...[123]}</h1>;
Output:
- error: Unexpected ...
+ var a = jsx_dev_runtime.jsxDEV("h1", {
+ children: [...[123]]
+ }, undefined, true, undefined, this);
Stack Overflow in already-minified code
"Stack Overflow" is not just a website. Programs execute code in a stack, and if the stack uses too much memory, it overflows.
Bun's parser is a recursive descent parser, which means it uses recursion to parse code. This makes the implementation simpler, but it can lead to stack overflow if the input source code is too deeply nested with binary expressions. This release fixes that.
For example, if you were to write a file with contents like this:
const chain =
`globalThis.a = {};` +
"\n" +
`globalThis.a + globalThis.a +`.repeat(1000000) +
`globalThis.a` +
"\n";
await Bun.write("stack-overflow.js", chain);
And then run it with bun stack-overflow.js, it would crash in bun's transpiler due to stack overflow.
This has now been fixed, thanks to @dylan-conway.
This bug manifested when using Bun's runtime to format a TypeScript file with Prettier.
override keyword in TypeScript constructor
There was a bug where the override keyword in a TypeScript constructor would cause an unexpected error.
class Foo {}
class Bar extends Foo {}
class Fizz {
constructor(readonly foo: Foo) {}
}
class Buzz extends Fizz {
constructor(override bar: Bar) {
super(foo);
}
}
new Buzz(new Bar());
- 10 | constructor(override foo: FooChild) {
- ^
- error: Expected ")" but found "foo"
- at example.ts:10:23
Fixed: Dynamicly loaded CommonJS modules in bun build --compile
When the argument to require is not statically analyzable, bun build leaves it as is.
const dynamic = (name) => require(name);
console.log(dynamic("some-package"));
If you used this with bun build --compile and loaded a commonjs file this way, the transpiler would generate code with a syntax error. This has been fixed.
Thanks to 25 contributors!
We're always excited to welcome new contributors to Bun. 12 new contributors made their first contribution in this release.
- @adrienbrault made their first contribution in #7350
- @brianknight10 made their first contribution in #7368
- @DontBreakAlex made their first contribution in #7316
- @james-elicx made their first contribution in #7277
- @joeyw made their first contribution in #7364
- @lqqyt2423 made their first contribution in #7089
- @mimikun made their first contribution in #7378
- @NReilingh made their first contribution in #7372
- @rhyzx made their first contribution in #7294
- @RiskyMH made their first contribution in #7306
- @stav made their first contribution in #7327
- @SukkaW made their first contribution in #7348
- @yharaskrik made their first contribution in #7409
Thanks to the 25 contributors who made this release possible: