---
title: Bun v1.1.39
description: "Fixes 61 bugs (addressing 99 👍). bun.lock is bun's new text-based lockfile. Cached bun install gets 30% faster. fetch() request body streams. 100% of string_decoder, punycode and querystring Node.js tests pass. Native plugin API for Bun.build() and Rust crate. Implemented expect().toMatchInlineSnapshot(). More accurate heap snapshots. WebSocket server memory reduction. WebKit Upgrade with Error.isError, faster String.prototype.at."
date: "2024-12-17T11:19:14.373Z"
author: jarred
---

{% callout %}
We're hiring [systems engineers](/careers) in San Francisco to build the future of JavaScript!
{% /callout %}

This release fixes 61 bugs (addressing 99 👍). It introduces `bun.lock`, a new text-based lockfile format. It makes cached `bun install` 30% faster. It adds support for `fetch()` request body streams. It brings Node.js' `string_decoder`, `punycode` and `querystring` to 100% Node.js compatibility. It adds a native plugin API for `Bun.build()`, along with a Rust crate integrating with napi.rs. It implements `expect().toMatchInlineSnapshot()`. It adds more accurate heap snapshots. It reduces WebSocket server memory usage. It upgrades WebKit bringing `Error.isError`, faster `String.prototype.at`, and more.

#### To install Bun

{% codetabs %}

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

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

```sh#powershell
$ powershell -c "irm bun.sh/install.ps1|iex"
```

```sh#scoop
$ scoop install 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
```

## `bun.lock` is bun's new text-based lockfile

In this release, we've added a new human-readable lockfile format: `bun.lock`. This makes git diffs and merge conflicts work better, and unblocks tooling like Dependabot, Renovate, Turbo prune, and more.

To try it out, run:

```sh
bun install --save-text-lockfile
```

**[We wrote a blog post about `bun.lock`](/blog/bun-lock-text-lockfile)**

Along the way, we also **made cached `bun install` 30% faster**. Specifically, when the packages were previously installed and it needs to verify the versions of every installed package to ensure they haven't changed (a common scenario when running `bun install` locally).

Big thanks to [@dylan-conway](https://github.com/dylan-conway) for implementing this!

## fetch() request body streams

You can now send `fetch()` `Request` bodies as streams. Previously, only `Response` bodies could be streamed in the client (Bun.serve() always supported both).

This uses HTTP/1.1 `Transfer-Encoding: chunked` on the client. When the content length is known ahead of time, it uses `Content-Length` instead.

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun<br><br>fetch()&#39;s `body` supports streaming ReadableStream, Node.js streams and async generator* functions <a href="https://t.co/s6V2ugfEkK">pic.twitter.com/s6V2ugfEkK</a></p>&mdash; Bun (@bunjavascript) <a href="https://twitter.com/bunjavascript/status/1864497708658249749?ref_src=twsrc%5Etfw">December 5, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

Thanks to [@cirospaciari](https://github.com/cirospaciari) for implementing this!

## Node.js Compatibility

### Fixed: Hang in connection error on `postgres` package

Two different bugs could lead to a hang in the `postgres` package:

- When an `AbortSignal` passed to `net.connect` signals, Bun was calling `destroy` on the `net.Socket` instead of emitting an `abort` event. This has been fixed, thanks to [@cirospaciari](https://github.com/cirospaciari)!
- Calling `pause` on a `net.Socket` that came from an upgraded TLS connection could cause the underlying socket to be paused instead of just the node:stream.

Thanks to [@cirospaciari](https://github.com/cirospaciari) for the fix!

### 100% of Node's `string_decoder` tests pass

We fixed an edgecase in the `"string_decoder"` module's handling of incomplete multi-byte characters to align with Node.js, and now all Node.js' string_decoder tests pass.

Thanks to [@paperclover](https://github.com/paperclover)!

### 100% of Node's `punycode` tests pass

Bun is now fully compatible with Node.js punycode implementation.

Thanks to [@snoglobe](https://github.com/snoglobe)!

### 100% of Node's `querystring` tests pass

We fixed some edgecases in the `"querystring"` module to align with Node.js.

Thanks to [@pfgithub](https://github.com/pfgithub)!

### Improved node:path test coverage

All but one of Node's `path` tests now pass (the one for the newly-added method, `path.matchesGlob` is not implemented yet).

Thanks to [@DonIsaac](https://github.com/DonIsaac)!

### Improved node:os test coverage

Only one test failure remains in the `os` module.

### Fixed: `crypto.createHash(alg, options)` missing option

Thanks to [@heimskr](https://github.com/heimskr)!

### Fixed: `napi_wrap`

The `napi_wrap` function now behaves the same as in Node.js.

Thanks to [@190n](https://github.com/190n)!

### Fixed: error code in node:zlib

Bun was not throwing precisely the same `error.code` properties for zlib errors as Node.js. This has been fixed, thanks to [@nektro](https://github.com/nektro)!

### Fixed: `fs.readFileSync` with `flag` parameter in object

The following would incorrectly ignore the `flag` parameter:

```js
import fs from "fs";
fs.readFileSync("data.txt", { encoding: "utf8", flag: "w+" });
```

This has been fixed, thanks to [@pfgithub](https://github.com/pfgithub)!

## Bundler improvements

### `onBeforeParse` - Rust/C/Zig plugin API

This release introduces a new zero-copy native plugin API for `Bun.build()`. Unlike our JavaScript plugin API, this runs inside Bun's threadpool immediately before parsing, without cloning the source code, without undergoing string conversion, and with practically zero overhead.

```sh
bun add -g @napi-rs/cli
napi new
cargo add bun-native-plugin
```

From there, you can implement the `onBeforeParse` hook:

```rust#lib.rs
use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader};
use napi_derive::napi;

/// Define the plugin and its name
define_bun_plugin!("replace-foo-with-bar");

/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
/// `foo` with `bar`.
///
/// We use the #[bun] macro to generate some of the boilerplate code.
///
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
/// the macro that this function implements the `onBeforeParse` hook.
#[bun]
pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> {
  // Fetch the input source code.
  let input_source_code = handle.input_source_code()?;

  // Get the Loader for the file
  let loader = handle.output_loader();


  let output_source_code = input_source_code.replace("foo", "bar");

  handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX);

  Ok(())
}
```

To use in `Bun.build()`:

```typescript
import myNativeAddon from "./my-native-addon";
Bun.build({
  entrypoints: ["./app.tsx"],
  plugins: [
    {
      name: "my-plugin",

      setup(build) {
        build.onBeforeParse(
          {
            namespace: "file",
            filter: "**/*.tsx",
          },
          {
            napiModule: myNativeAddon,
            symbol: "replace_foo_with_bar",
            // external: myNativeAddon.getSharedState()
          },
        );
      },
    },
  ],
});
```

This plugin API is designed for use with NAPI addons so that existing libraries can add these without thinking much about how to initialize them.

Thanks to [@zackradisic](https://github.com/zackradisic) for implementing this!

### CSS parser improvements

Several CSS parser and printer edge cases have been fixed, thanks to [@zackradisic](https://github.com/zackradisic)!

### Inject environment variables in `Bun.build()`

This release adds support for injecting environment variables in `Bun.build()` and `bun build`:

{% codetabs %}

```typescript#API
// In Bun.build()
await Bun.build({
  entrypoints: ['./app.tsx'],
  outdir: './out',

  // All environment variables starting with "PUBLIC_"
  // will be injected in the build as process.env.PUBLIC_*
  env: "PUBLIC_*",

  // eg
  // console.log(process.env.PUBLIC_FOO); => "bar"
});

await Bun.build({
  entrypoints: ['./app.tsx'],
  outdir: './out',

  // Inject all environment variables in the build when used.
  env: "inline",
});
```

```sh#CLI
bun build ./app.tsx --env='PUBLIC_*'
bun build ./app.tsx --env=inline
```

{% /codetabs %}

## Inline snapshot tests

This release implements `expect().toMatchInlineSnapshot()` in `bun:test`. Inline snapshots are stored directly in the test file, and are updated with `bun test -u`.

```typescript
import { expect, test } from "bun:test";

test("format user profile", () => {
  const result = formatProfile({
    name: "Jarred",
    role: "admin",
  });

  // Snapshot stored directly in test file
  expect(result).toMatchInlineSnapshot(`{
    "displayName": "Jarred",
    "permissions": ["admin"],
    "createdAt": "2024-12-13T00:00:00.000Z"
  }`);
});

test("validate input", () => {
  // Match error messages
  expect(() => validateInput({})).toThrowErrorMatchingInlineSnapshot(`
      "Invalid input:
       - Missing required field: name"
    `);
});
```

Update snapshots with:

```bash
bun test -u
```

We've also added:

- `expect().toThrowErrorMatchingSnapshot()`
- `expect().toThrowErrorMatchingInlineSnapshot()`

Thanks to [@pfgithub](https://github.com/pfgithub) for implementing this!

## Memory Profiler improvements

We've implemented support for reporting memory usage from native code in our JavaScriptCore <> Zig class bindings generator. This makes heap snapshots reporting more accurate:

- `Blob`, `Request`, and `Response` now report the size including their body, URL, and underlying structs instead of only the JavaScript wrapper object sizes.
- `URLSearchParams` and `Headers` now report the size of size of the C++ classes along with the size of each string.
- `ServerWebSocket` and `WebSocket` now reports the size of the underlying structs and buffered data
- `FormData`'s size now includes the size of each string, the underlying C++ classes, and the size of any files contained in it.
- `Subprocess` (`Bun.spawn`) reports the size of the underlying structs and buffered data
- `Bun.file().writer()` reports the size of buffered data
- All other classes implemented in Zig report their internal struct's size

This does not impact memory usage from the garbage collector's perspective or from the runtime's perspective, but this does make it easier for you to debug what's taking up memory in applications using Bun.

### New: `estimateShallowMemoryUsageOf` in `"bun:jsc"`

The `estimateShallowMemoryUsageOf` function in `"bun:jsc"` estimates the memory usage of a JavaScript value without including the memory usage of its children, properties, or internal slots.

```ts
import { estimateShallowMemoryUsageOf } from "bun:jsc";

const obj = { foo: "bar" };
const usage = estimateShallowMemoryUsageOf(obj);
console.log(usage); // => 16

const buffer = Buffer.alloc(1024 * 1024);
estimateShallowMemoryUsageOf(buffer);
// => 1048624
```

### WebSocket server memory reduction

This release reduces the memory usage of Bun's builtin WebSocket server `Bun.serve()`, particularly for long-running processes that open and close many WebSocket server connections.

In this microbenchmark that opens and closes 200,000 WebSocket connections in loops, the peak RSS drops by 15%.

{% details summary="Microbenchmark" %}

```ts
const batchSize = 500;
let onClosePromise = Promise.withResolvers();
let onCloseCount = 0;
using server = Bun.serve({
  port: 0,
  fetch(req, server) {
    return server.upgrade(req);
  },
  websocket: {
    open(socket) {},
    drain(ws) {},
    close(ws) {
      onCloseCount++;
      if (onCloseCount === batchSize) {
        onClosePromise.resolve();
      }
    },
  },
});

async function batch() {
  const clients = new Array(batchSize);
  const promises = new Array(batchSize + 1);
  onCloseCount = 0;
  onClosePromise = Promise.withResolvers();

  console.time(`Batch of ${batchSize}`);

  for (let i = 0; i < batchSize; i++) {
    const client = new WebSocket(server.url);
    const { promise, resolve, reject } = Promise.withResolvers();
    clients[i] = client;
    promises[i] = promise;
    client.onclose = () => {
      resolve();
    };
    client.onopen = () => {
      client.close();
    };
  }
  promises[batchSize] = onClosePromise;
  await Promise.all(promises);
  Bun.gc(true);
  console.timeEnd(`Batch of ${batchSize}`);
  console.log(`RSS:`, (process.memoryUsage.rss() / 1024 / 1024) | 0, "MB");
}

for (let i = 0; i < 400; i++) {
  Bun.gc(true);
  const heapStats = require("bun:jsc").heapStats();
  for (const key in heapStats.zones) {
    if (heapStats.zones[key] < 2048 * 1024) delete heapStats.zones[key];
  }
  for (const key in heapStats.objectTypeCounts) {
    if (heapStats.objectTypeCounts[key] < 100) delete heapStats.objectTypeCounts[key];
  }
  console.log(heapStats);
  await batch();
}
Bun.gc(true);
const rss = process.memoryUsage.rss();
console.log(`Initial RSS: ${(rss / 1024 / 1024) | 0} MB`);

for (let i = 0; i < 200; i++) {
  Bun.gc(true);
  const heapStats = require("bun:jsc").heapStats();
  for (const key in heapStats.zones) {
    if (heapStats.zones[key] < 2048 * 1024) delete heapStats.zones[key];
  }
  for (const key in heapStats.objectTypeCounts) {
    if (heapStats.objectTypeCounts[key] < 100) delete heapStats.objectTypeCounts[key];
  }
  console.log(heapStats);
  await batch();
}
Bun.gc(true);
const finalRss = process.memoryUsage.rss();
console.log(`Final RSS: ${(finalRss / 1024 / 1024) | 0}MB`);
```

{% /details %}

## Dependency upgrades

We've updated several dependencies:

- c-ares to v1.34.3
- lshpack to v2.3.3
- libdeflate to v1.22
- SQLite to 3.470.200
- BoringSSL

## WebKit Upgrade

This release upgrades the internal version of JavaScriptCore from upstream WebKit, which includes improvements to loop unrolling, `Intl.PluralRules`, and `Intl.NumberFormat`, and more.

### Error.isError

The `Error.isError(object)` method returns `true` if the `object` is really an `Error` instance.

```js
Error.isError(new Error()); // => true
Error.isError({}); // => false
Error.isError(new Error("foo")); // => true
Error.isError(new Error("foo").message); // => false
Error.isError({ [Symbol.toStringTag]: "Error" }); // => false
Error.isError(new (class Error {})()); // => false
Error.isError({ constructor: function Error() {} }); // => false
```

[`Error.isError` is a stage3 TC39 proposal](https://github.com/tc39/proposal-is-error).

### Faster String.prototype.at

{% raw %}

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">In the next version of Bun &amp; Safari<br><br>`&quot;foo&quot;.at(i)` gets 44% faster, thanks to <a href="https://twitter.com/__sosukesuzuki?ref_src=twsrc%5Etfw">@__sosukesuzuki</a> <a href="https://t.co/UtkkJSp6Vb">pic.twitter.com/UtkkJSp6Vb</a></p>&mdash; Bun (@bunjavascript) <a href="https://twitter.com/bunjavascript/status/1867203604777676961?ref_src=twsrc%5Etfw">December 12, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

{% /raw %}

## Bug Fixes

### Fixed: Debugger flakiness in VSCode

The process would sometimes exit before the debugger could attach. Please let us know if you're still experiencing this!

This has been fixed, thanks to [@RiskyMH](https://github.com/RiskyMH)

### Fixed: Crash in POSIX signal handling

A crash or deadlock that could occur occur after registering a signal handler like `process.on('SIGINT')` or `process.on('SIGTERM')` and repeatedly receiving signals has been fixed.

### Fixed: Rare file descriptor leak in Bun Shell and `Bun.spawn`

A theoretical file descriptor leak in Bun Shell and `Bun.spawn` has been fixed. This could potentially happen when spawning processes on other threads in older versions of Linux.

It turns out we were also not always correctly marking file descriptors as non-blocking, which also has been fixed.

### Fixed: fetch redirect handling with `"Connection: close"`

When `fetch()` redirects with `"Connection": "close"`, Bun could potentially report a connection error instead of handling the redirect as expected. Thanks to [@cirospaciari](https://github.com/cirospaciari) for the fix!

### Fixed: Crash in bun install with invalid package.json in workspace

A crash that could occur when running `bun install` on a workspace with invalid `package.json` files has been fixed.

### Fixed: rare crash in Bun.build() plugins

A rare crash that could occur in Bun.build() plugins has been fixed. This would potentially happen if using the `"file"` loader in a plugin.

### Fixed: crash in Bun.build() with a lot of comments in TypeScript files

A crash that could occur in certain cases when many comments were present in TypeScript files has been fixed, thanks to [@dylan-conway](https://github.com/dylan-conway)!

### Fixed: global .npmrc not using auth

Auth configuration was only applied per .npmrc file and then discarded. Therefore, auth defined in the global .npmrc wouldn't apply to registries in the project .npmrc.

This has been fixed, thanks to [@robertshuford](https://github.com/robertshuford)!

## Thanks to 20 contributors!

- [@01101sam](https://github.com/01101sam)
- [@190n](https://github.com/190n)
- [@cirospaciari](https://github.com/cirospaciari)
- [@DonIsaac](https://github.com/DonIsaac)
- [@dylan-conway](https://github.com/dylan-conway)
- [@Electroid](https://github.com/Electroid)
- [@eventualbuddha](https://github.com/eventualbuddha)
- [@heimskr](https://github.com/heimskr)
- [@hex2f](https://github.com/hex2f)
- [@imide](https://github.com/imide)
- [@Jarred-Sumner](https://github.com/Jarred-Sumner)
- [@nattui](https://github.com/nattui)
- [@nektro](https://github.com/nektro)
- [@paperclover](https://github.com/paperclover)
- [@pfgithub](https://github.com/pfgithub)
- [@RiskyMH](https://github.com/RiskyMH)
- [@robertshuford](https://github.com/robertshuford)
- [@snoglobe](https://github.com/snoglobe)
- [@swen128](https://github.com/swen128)
- [@zackradisic](https://github.com/zackradisic)
