---
title: Bun v1.0.15
description: "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()`, TensorFlow.js support, fs.readdir recursive is 40x faster than in Node, and more."
date: "2023-12-02T14:30:15.264Z"
author: ashcon
draft: false
---

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 &mdash; all in one. In case you missed it, here are some of the recent changes to Bun:

- [`v1.0.12`](/blog/bun-v1.0.12) - Adds `bun -e` for evaluating scripts, `bun --env-file` for loading environment variables, `server.url`, `import.meta.env`, `expect.unreachable()`, improved CLI help output, and more
- [`v1.0.13`](/blog/bun-v1.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 bug
- [`v1.0.14`](/blog/bun-v1.0.14) - `Bun.Glob`, a fast API for matching files and strings using glob patterns. It also fixes a race condition when extracting dependencies during `bun install`, improves TypeScript module resolution in `node_modules`, and makes error messages easier to read.

To install Bun:

{% codetabs %}

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

```sh#npm
$ npm install -g bun
```

```sh#brew
$ brew tap oven-sh/bun
$ brew install bun
```

```sh#docker
$ docker pull oven/bun
$ docker run --rm --init --ulimit memlock=-1:-1 oven/bun
```

{% /codetabs %}

To upgrade Bun:

```sh
$ 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:

```rust
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)

```rust
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`](https://developer.mozilla.org/en-US/docs/Web/API/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.

```js
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](https://www.google.com/search?q=autobahn+websocket&sourceid=chrome&ie=UTF-8) 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.)

![](https://github.com/oven-sh/bun/assets/3238291/a0d322fe-4deb-4970-a439-a179184559a0)

That means all of these [issues](https://github.com/oven-sh/bun/issues/6686), and more, have now been fixed.

![](https://github.com/oven-sh/bun/assets/3238291/aef6ec18-72ce-4ff1-821d-fac476510d48)

Thanks to [@cirospaciari](https://github.com/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()`](https://jestjs.io/docs/expect#expectextendmatchers). 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:

```js
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:

- [`expect.closeTo()`](https://jestjs.io/docs/expect#expectclosetonumber-numdigits)
- [`expect.objectContaining()`](https://jestjs.io/docs/expect#expectobjectcontainingobject)

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:

```js
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](https://github.com/otgerrogla) for implementing these features and also fixing various bugs with `expect()`.

There are also more mock matchers available, including:

- [`expect().toHaveBeenCalledWith()`](https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-)
- [`expect().toHaveBeenLastCalledWith()`](https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-)
- [`expect().toHaveBeenNthCalledWith()`](https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-)

You can use these matchers to check if a function was called with certain arguments:

```js
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](https://github.com/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.

![Syntax errors](https://github.com/oven-sh/bun/assets/709451/a95254d0-2652-433c-80e7-7faac1e38c2a)

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.

![](https://github.com/oven-sh/bun/assets/24465214/0e7166bf-2fe2-40e8-9c20-98135ed8d691)

Thanks to [@paperclover](https://github.com/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.

```diff
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.

```js
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.

```js
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.

![](https://github.com/oven-sh/bun/assets/3238291/31feab1d-8dce-4aec-9909-0a197e1b37e9)

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Fixed.<a href="https://t.co/WhuVBhNdc3">https://t.co/WhuVBhNdc3</a></p>&mdash; Jarred Sumner (@jarredsumner) <a href="https://twitter.com/jarredsumner/status/1728042351010910323?ref_src=twsrc%5Etfw">November 24, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

## CommonJS modules get 1% faster

We made some improvements to how Bun loads CommonJS modules, which makes them 1% faster.

![](https://github.com/oven-sh/bun/assets/3238291/c22b6a4d-7b5c-4625-a192-22bc6244fa1d)

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](https://github.com/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](https://www.tensorflow.org/js) from working. This has now been fixed.

```js
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.

```js
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](https://github.com/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.

```sh
❯ 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](https://github.com/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.

```diff
console.log({ a: 1, b: 2, c: "3" });
{
   a: 1,
   b: 2,
-  c: "3"
+  c: "3",
}
```

Thanks to [@hustLer2k](https://github.com/hustLer2k) for implementing this improvement, and to [@ArnaudBarre](https://github.com/ArnaudBarre) for the suggestion!

## Implemented: `console.timeLog`

There was a bug that prevented `console.timeLog` from working. This was fixed thanks to [@lqqyt2423](https://github.com/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.

```js
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](https://github.com/samfundev).

```js
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.

```js
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.

```diff
content-range: bytes 0-1000/10000
- content-range: bytes 0-1000/*
```

This was fixed thanks to [@libersoft-org](https://github.com/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](https://github.com/DontBreakAlex).

```json#tsconfig.json
{
  "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](https://github.com/rhyzx).

Input:

```jsx
const a = <h1>{...[123]}</h1>;
```

Output:

```diff
- 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:

```js
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](https://github.com/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.

```ts
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());
```

```diff
- 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.

```ts
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](https://github.com/adrienbrault) made their first contribution in [#7350](https://github.com/oven-sh/bun/pull/7350)
- [@brianknight10](https://github.com/brianknight10) made their first contribution in [#7368](https://github.com/oven-sh/bun/pull/7368)
- [@DontBreakAlex](https://github.com/DontBreakAlex) made their first contribution in [#7316](https://github.com/oven-sh/bun/pull/7316)
- [@james-elicx](https://github.com/james-elicx) made their first contribution in [#7277](https://github.com/oven-sh/bun/pull/7277)
- [@joeyw](https://github.com/joeyw) made their first contribution in [#7364](https://github.com/oven-sh/bun/pull/7364)
- [@lqqyt2423](https://github.com/lqqyt2423) made their first contribution in [#7089](https://github.com/oven-sh/bun/pull/7089)
- [@mimikun](https://github.com/mimikun) made their first contribution in [#7378](https://github.com/oven-sh/bun/pull/7378)
- [@NReilingh](https://github.com/NReilingh) made their first contribution in [#7372](https://github.com/oven-sh/bun/pull/7372)
- [@rhyzx](https://github.com/rhyzx) made their first contribution in [#7294](https://github.com/oven-sh/bun/pull/7294)
- [@RiskyMH](https://github.com/RiskyMH) made their first contribution in [#7306](https://github.com/oven-sh/bun/pull/7306)
- [@stav](https://github.com/stav) made their first contribution in [#7327](https://github.com/oven-sh/bun/pull/7327)
- [@SukkaW](https://github.com/SukkaW) made their first contribution in [#7348](https://github.com/oven-sh/bun/pull/7348)
- [@yharaskrik](https://github.com/yharaskrik) made their first contribution in [#7409](https://github.com/oven-sh/bun/pull/7409)

Thanks to the 25 contributors who made this release possible:

- [@adrienbrault](https://github.com/adrienbrault)
- [@brianknight10](https://github.com/brianknight10)
- [@cirospaciari](https://github.com/cirospaciari)
- [@Didas-git](https://github.com/Didas-git)
- [@DontBreakAlex](https://github.com/DontBreakAlex)
- [@dylan-conway](https://github.com/dylan-conway)
- [@Electroid](https://github.com/Electroid)
- [@Hanaasagi](https://github.com/Hanaasagi)
- [@hustLer2k](https://github.com/hustLer2k)
- [@james-elicx](https://github.com/james-elicx)
- [@Jarred-Sumner](https://github.com/Jarred-Sumner)
- [@jerome-benoit](https://github.com/jerome-benoit)
- [@joeyw](https://github.com/joeyw)
- [@lqqyt2423](https://github.com/lqqyt2423)
- [@mimikun](https://github.com/mimikun)
- [@NReilingh](https://github.com/NReilingh)
- [@otgerrogla](https://github.com/otgerrogla)
- [@paperclover](https://github.com/paperclover)
- [@rhyzx](https://github.com/rhyzx)
- [@RiskyMH](https://github.com/RiskyMH)
- [@samfundev](https://github.com/samfundev)
- [@stav](https://github.com/stav)
- [@SukkaW](https://github.com/SukkaW)
- [@WingLim](https://github.com/WingLim)
- [@yharaskrik](https://github.com/yharaskrik)
- [@zackradisic](https://github.com/zackradisic)
