Bun

Bun v1.1.21


Ashcon Partovi · July 27, 2024

Bun v1.1.21 is here! This release fixes 72 bugs (addressing 63 👍). 30% faster fetch() decompression, New --fetch-preconnect flag, improved Remix support, Bun gets 4 MB smaller on Linux, bundle packages excluding dependencies, many bundler fixes and node compatibility improvements.

We're hiring systems engineers in San Francisco to build the future of JavaScript!

Previous releases

  • v1.1.20 & v1.1.19 Fixes 54 bugs (addressing 248 👍). JavaScript gets faster on Windows. Raspberry Pi 4 support. _auth support in .npmrc. bun install preserves package.json indentation. aws-cdk-lib support. Memory leak fixes in new Response(request) and fs.readdir. Several reliability improvements.
  • v1.1.18 Fixes 55 bugs (addressing 493 👍). Support for npmrc. Improved constant folding & enum inlining. Improved console.log output for functions. Fixes patching dependencies in workspaces. Fixes several bugs in bun install when linking binaries. Fixes a crash in dns.lookup, and normalizes DNS errors to better match Node. Fixes an assertion failure in Bun.escapeHTML. Upgrades JavaScriptCore.
  • v1.1.0 Bundows. Windows support is here!

To install Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install 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

fetch() decompresses 30% faster

Decompressing fetch() response bodies compressed with gzip or deflate get 30% faster on Linux x64, thanks to libdeflate.

We've also enabled libdeflate with the websocket server, to make decompressing small messages faster.

New: --fetch-preconnect warms up a connection ahead of time

We've added a new flag --fetch-preconnect=<url> <file> that starts up an HTTP request to the given URL before any code is executed.

bun --fetch-preconnect='https://example.com' ./index.ts

For many production services, the first thing a script in Bun does is send HTTP requests to a server using fetch or node:http. The slowest part of many network requests is the initial connection setup, which can involve DNS lookups, TCP connection establishment, and TLS negotiation. Sometimes this takes 100ms before sending the HTTP request.

This work is network-bound. What if you could start up that connection before any code is executed, so that by the time the rest of your code runs, the connection is already established? This is what --fetch-preconnect does. It's a lot like <link rel="preconnect"> in HTML, but for server-side JavaScript.

Along the way, we've also added a JavaScript API to do the same thing:

fetch.preconnect(url: string): void;

This is useful for serverless environments and databases that run on top of fetch or node:http.

 bun --fetch-preconnect=https://example.com ./hey.mjs
[8.37ms] fetch(https://example.com)

❯ bun hey.mjs # Without preconnect
[51.92ms] fetch(https://example.com)

❯ node hey.mjs
fetch(https://example.com): 68.417ms

❯ cat hey.mjs
       │ File: hey.mjs
   1if (globalThis.Bun) {
   2   │   await Bun.sleep(1000);
   3   │ } else {
   4   │   await new Promise(resolve => setTimeout(resolve, 1000));
   5   │ }
   6
   7   │ console.time("fetch(https://example.com)");
   8await fetch("https://example.com");
   9   │ console.timeEnd("fetch(https://example.com)");

Improved Remix support

We fixed a bug where overriding globals like Request or Response could cause issues with node:http. Internally, Bun's implementation of node:http uses Bun.serve() which expects the user to return the native Response object.

Remix and other frameworks globally polyfill Request and Response, and Bun's node:http was previously using the global Request and Response objects. We fixed this by always using the native Request and Response objects in node:http.

We also fixed similar issues in Bun's polyfill of node-fetch and undici.

If you experienced the following error, it has now been fixed:

// error: Expected a Response object, but received 'Response {
//   [Symbol(Body internals)]: {
//     body: ReadableStream {
// ...

We also added an integration test for Remix, so we can continue to ensure compatibility in the future.

bun install retries on HTTP 500 errors

In this release, bun install will now retry on network errors with an HTTP status code > 499.

We've seen reports from users and from our own CI where the official npm registry returned HTTP 500 errors, which would cause bun install to fail. Now, bun install will retry on these errors, which should help compensate for temporary issues with the npm registry (reducing spurious CI failures).

4 MB smaller on Linux

In this release, we've shrunk Bun's Linux x64 binary size by 4 MB, which makes it 23% smaller than the official Node.js v22.5.1 executable.

Binary SizeVersion
92 MBBun v1.1.21
96 MBBun v1.1.20
97 MBBun v1.1.19
97 MBBun v1.1.18
114 MBNode.js v22.5.1

1ms faster on Linux

We've also improved Bun's startup time on Linux by 1ms.

hyperfine "bun --reivison" "bun-1.1.20 --revision"

Benchmark 1: bun --revision
  Time (mean ± σ):       1.4 ms ±   0.2 ms    [User: 0.9 ms, System: 0.4 ms]
  Range (min … max):     1.2 ms …   2.6 ms    1143 runs

Benchmark 2: bun-1.1.20 --revision
  Time (mean ± σ):       2.3 ms ±   0.2 ms    [User: 1.1 ms, System: 1.1 ms]
  Range (min … max):     2.2 ms …   5.0 ms    1099 runs

These size & start time improvements came from a combination of linker flags, compiler flags, link-time optimization and a few small code changes.

Bundle packages excluding dependencies with bun build --packages=external

You can now control if package dependencies are included in your bundle or not. If the import does not start with ., .. or /, then it is considered a package.

CLI
bun build ./index.ts --packages external

You can also use this in the JavaScript API:

JavaScript
await Bun.build({
  entrypoints: ["./index.ts"],
  packages: "external",
});

This is useful when bundling libraries. It lets you reduce the number of files your users have to download, while continuing to support peer or external dependencies.

Thanks to @zpix1 for implementing this feature!

Improved hashes for code-splitting in bun build

Previously, Bun would generated "isolated" hashes for files during code-splitting, so it can be done in parallel. However, this would mean that if an imported file was changed, the hash of the file that owns the import would not change.

entry.ts
const other = await import('./second');
second.ts
export const second = 1;

This has been fixed, and hashes are also shorter by switching to a base32 encoding, similar to how esbuild does it.

Thanks to @paperdave for working on this!

Fixed: Import resolves as .mjs when file is .mts

We fixed a bug where a .mjs import would not resolve to a .mts file.

bar.mts
import "./foo.mjs";
foo.mts
export const foo = 1;

This has now been fixed thanks to @190n!

Fixed: Crash with Bun.write on Windows

We fixed a bug in Bun.write() on Windows, where Bun could crash after creating the directory for a file that didn't exist.

import { write, file } from "bun";

const txt = file("/does/not/exist/example.txt");
await write(txt, "hello world");

Fixed: setSystemTime would not work with a number

There was a bug where using setSystemTime from the bun:test module would not work with a number, instead of a Date object.

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

test("setSystemTime", () => {
  const future = Date.now() + 1000;
  setSystemTime(future);

  expect(Date.now()).toBe(future);
  // Before: <test failed>
  // After: <test passed>
});

Thanks to @cirospaciari for fixing this bug!

Fixed: Crash when merging nested TypeScript namespaces

There was a bug in Bun where a TypeScript namespaces could crash Bun. This was an edge-case in Bun's transpiler where it was not properly handling when a function would merge with a class or namespace.

namespace X {
  export function Y(): void {}
  export namespace Y {
    export const Z = 1;
  }
}

Thanks to @paperdave for fixing this bug!

This has now been fixed thanks to @190n!

Fixed: Crash with aborted request after server closed

We fixed a rare crash in Bun.serve() that could occur if a Request was manually aborted after the the server was closed before the request finished sending the body.

Thanks to @cirospaciari for fixing this bug!

Fixed: Crash with empty value in CryptoHasher.update()

We fixed a bug in CryptoHasher.update() where an empty value would cause a crash.

import { CryptoHasher } from "bun";

const hasher = new CryptoHasher("sha1");
hasher.update(); // <crash>

Thanks to @cirospaciari for fixing this bug!

Fixed: Crash while printing error stacks

A crash could occur when printing stack traces in Bun. This was caused by not correctly checking if the stackframe came from a JavaScript function or from a native function.

Thanks to @nektro for fixing this bug!

Fixed: using expect.any() with expect.toThrow()

The expected value for expect.toThrow() can now use asymmetric matcher expect.any(). The following code will work as expected:

test("toThrow asymmetric matcher", () => {
  expect(() => {
    throw new Error("error!");
  }).toThrow(expect.any(Error)); // passes
});

Thanks to @ippsav!

Fixed a crash with the return value of test.each in bun test

There was a bug in bun test where a null pointer could be used for the return value of test.each, resulting in a crash if the value was used in JavaScript. Now, test.each will return undefined.

console.log(test.each([1, 2])("test.each %d", () => {})); // Before: <crash>, After: undefined

Thanks to @dylan-conway!

Fixed: Code coverage on Windows now excludes node_modules

We fixed a bug in bun test --coverage where the code coverage report would include files from node_modules on Windows. This was caused by only checking / as the path separator, when on Windows it may either be \ or /.

Thanks to @dariushalipour for fixing this bug!

Node.js compatibility improvements

Fixed: Silent errors in node:http request 'data' callback

A bug causing errors to be silent in the data callback of a node:http client request has been fixed.

Previously, the following code would not error:

const { request } = require("node:http");
const req = request("http://www.google.com", (res) => {
  res.on("data", (chunk) => {
    throw new Error("oopsie");
  });
}).end();

Now, it correctly throws an error:

1 | const { request } = require("node:http");
2 | const req = request("http://www.google.com", (res) => {
3 |   res.on("data", (chunk) => {
4 |     throw new Error("oopsie");
                  ^
error: oopsie
      at silent.js:4:15
      at emit (node:events:180:48)
      at addChunk (node:stream:2029:22)
      at readableAddChunk (node:stream:1983:30)
      at node:http:136:44

Bun v1.1.21 (macOS arm64)

Fixed: Readable.fromWeb stopped early with fetch() streams

We fixed a bug where Readable.fromWeb when passed a ReadableStream from fetch() would stop reading early in some cases if the stream was not finished being downloaded yet.

import { Readable } from "node:stream";

const res = await fetch("https://bun.sh");

const stream = Readable.fromWeb(res.body);

stream.on("data", (chunk) => {
  console.log("Received", chunk.length);
});

New:

❯ bun chunk.js
Received 16384
[... more chunks ...]
Received 3883

Previously:

❯ bun-1.1.20 chunk.js # Before

This bug specifically impacted fetch()'s ReadableStream.

Fixed: Allow undefined extension in path.basename()

We fixed a compatibility bug in path.basename() where it would not allow undefined as the extension.

import path from "path";

path.basename("foo", undefined);
path.posix.basename("bar", undefined);
path.win32.basename("baz", undefined);

If you encountered the following error, it has now been fixed:

> 2 | path.posix.basename("bar", undefined);
                          ^
TypeError: "ext" property must be of type string, got undefined
 code: "ERR_INVALID_ARG_TYPE"

Thanks to @190n!

Fixed: Unref in node:dgram before socket is connected

We fixed a bug in node:dgram where would not properly unref the UDP socket, if it was done before the socket was connected.

import dgram from "node:dgram";

const socket = dgram.createSocket("udp4");
socket.unref(); // <would not unref>

Thanks to @190n for fixing this bug!

Fixed: napi_threadsafe_function would keep the process alive after finalizing

We fixed a regression in Node-API where after a napi_threadsafe_function was finalized, it would continue to keep the process alive. Thanks to @dylan-conway for fixing this!

...
napi_threadsafe_function tsfn;
...
napi_release_threadsafe_function(tsfn, napi_tsfn_release);
// `tsfn` will allow the process to exit if all threads have released it.
...

Fixed: process.exitCode should be undefined until process.exit() is called

In Node.js, process.exitCode is undefined until either:

  • process.exitCode is set manually
  • process.exit() is called

We fixed a bug in Bun where process.exitCode would be set to 0 before process.exit() was called.

console.log(process.exitCode); // Before: 0, After: undefined

Thanks to @nektro for fixing this bug!

Fixed: Crash in node:zlib brotli decompression

A crash could occur when synchronously decompressing a brotli stream composed of many chunks over a long period of time. This has been fixed. The crash was caused by not properly keeping the value from JavaScript alive while the decompression was happening.

Thanks to @dylan-conway for fixing this bug!

Bundler fixes

Slightly smaller bundles

Fixed: Crash in bun build when bundling with external property access

We fixed a bug in bun build where there could be a crash when a module used property access on an imported external module.

This has been fixed thanks to @paperdave!

Fixed: Trailing slash in require() using bun build

We fixed a bug where a require() call with a trailing slash would not work when using bun build.

To match Node.js, if you try to require process/, it should resolve to node_modules/process/.... Previously, Bun was confused and resolved to the built-in node:process module.

var process2 = require("process/");
// Error: Cannot find module 'process/'

This has now been fixed thanks to @paperdave!

Fixed: import with rope strings crash

There was a bug in Bun where a non-literal value in with option on import() would crash Bun. This was because Bun's transpiler was not properly visiting the with option.

import("./foo", { with: "text" }); // would work
import("./foo", { with: "te" + "xt" }); // would crash

This has now been fixed, thanks to @paperdave!

Fixed: Class hoisting bug

A bug causing the following error has been fixed:

TypeError: The superclass is not a constructor.

When a class declaration has side effects, it cannot be hoisted. We fixed a bug where Bun's bundler would hoist a class declaration with side effects.

entry.js
other.js
entry.js
async function hi() {
  const { default: MyInherited } = await import('./other.js');
  const myInstance = new MyInherited();
  console.log(myInstance.greet())
}

hi();
other.js
const MyReassignedSuper = class MySuper {
  greet() {
    return 'Hello, world!';
  }
};

class MyInherited extends MyReassignedSuper {};

export default MyInherited;

This produces the correct output now:

diff.js
new.js
old.js
diff.js
-   class MyInherited extends MyReassignedSuper {
-   }
-   var MyReassignedSuper, other_default;
-     MyReassignedSuper = class MySuper {
-       greet() {
-         return "Hello, world!";
-       }
+ var MyReassignedSuper = class MySuper {
+   greet() {
+     return "Hello, world!";
+   }
+ }, MyInherited, other_default;
+   MyInherited = class MyInherited extends MyReassignedSuper {
+   };
+   other_default = MyInherited;
// ...
new.js
var __defProp = Object.defineProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);

// other.js
var exports_other = {};
__export(exports_other, {
  default: () => other_default
});
var MyReassignedSuper = class MySuper {
  greet() {
    return "Hello, world!";
  }
}, MyInherited, other_default;
var init_other = __esm(() => {
  MyInherited = class MyInherited extends MyReassignedSuper {
  };
  other_default = MyInherited;
});

// entry.js
async function hi() {
  const { default: MyInherited2 } = await Promise.resolve().then(() => (init_other(), exports_other));
  const myInstance = new MyInherited2;
  console.log(myInstance.greet());
}
hi();
old.js
var __defProp = Object.defineProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);

// other.js
var exports_other = {};
__export(exports_other, {
  default: () => other_default
});

class MyInherited extends MyReassignedSuper {
}
var MyReassignedSuper, other_default;
var init_other = __esm(() => {
  MyReassignedSuper = class MySuper {
    greet() {
      return "Hello, world!";
    }
  };
  other_default = MyInherited;
});

// entry.js
async function hi() {
  const { default: MyInherited2 } = await Promise.resolve().then(() => (init_other(), exports_other));
  const myInstance = new MyInherited2;
  console.log(myInstance.greet());
}
hi();

Internal: LLVM 18 on macOS & Windows

We've upgraded from LLVM 16 to LLVM 18 on macOS and Windows. This let us add more debug assertions on macOS which help catch some bugs earlier.

Thanks to 14 contributors!