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 -e
for evaluating scripts,bun --env-file
for 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 | bash
npm install -g bun
brew tap oven-sh/bun
brew install bun
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
To upgrade Bun:
bun upgrade
Transpiler 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.resolvesTo
expect.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 @paperdave 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 @paperdave 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: