Bun

Bun v1.1.22


Ashcon Partovi · August 7, 2024

Bun v1.1.22 is here! This release fixes 79 bugs (addressing 93 👍). Express is now 3x faster in Bun, ES modules load faster on Windows, 10% faster Bun.serve() at POST requests, source maps in bun build --compile. Memory usage reductions to --hot, Bun.serve(), and imports. Uint8Array.toBase64(), Uint8Array.toHex(), and lots of Node.js compatibiltiy improvements and bugfixes.

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

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

Express is now 3x faster in Bun

In this release, we made performance and compatibility improvements to Bun's node:http implementation. This increased express request throughput by 50% compared to Bun v1.1.21, which means its 3x faster in Bun than Node.js running the same code.

ES Modules load up to 4x faster on Windows

To make running TypeScript, JSX, ES modules, and CommonJS all just work in Bun, we transpile every file on the fly. This inherently has a performance cost.

This release brings concurrent transpilation support to Bundows, which makes loading ES modules faster.

async function benchmark(name) {
  console.time(name);
  await import(name);
  console.timeEnd(name);
}

await benchmark("lodash-es");

In this benchmark, we're running the above code and measuring the time it to import a popular package, like lodash-es.

RuntimeTime (ms)
Bun v1.1.2239
Bun v1.1.2182
Node.js v22.4.1186

This optimization previously was only supported on Bun for Linux & macOS, but now it is also supported on Windows.

10% higher throughput POST requests in Bun.serve()

If you code does not read the body of an incoming Request, there is a 10% performance improvement from the last release of Bun.

Less memory usage

bun --hot uses less memory

In this release, Bun uses 2x less memory when you use bun --hot to reload code.

Previously, we were not always releasing the source code for old versions of modules. This also applies if your code or dependencies modifies require.cache, which is a common pattern for reloading code.

Importing modules that are garbage collected uses less memory

We fixed a bug where importing or requiring a module that is garbage collected would keep a reference to that module's source code.

This was also caused when a module would throw an error, which would keep a reference to the module's source code.

Memory reductions

We also patched various memory leaks, including when:

  • requiring a CommonJS module
  • computing line offsets in source maps
  • transpiling more than 64 files, concurrently
  • creating a large number of setTimeout() or setInterval() timers

Better stack traces

We fixed a bug in how error.stack traces are rendered in Bun. Sometimes the source URL of a call site would be empty, which would cause the stack trace to be slightly different than what most tools expect to receive. Now, Next.js renders stack traces properly.

Before: Stack traces do not render in Next.js.
After: Stack traces show the correct source in Next.js.

Sourcemaps in single-file executables

bun build --compile lets you generate a single-file executable that bundles your source code, dependencies, and Bun into a single file you can deploy to production without needing other dependencies.

In this release of Bun, --sourcemap is now properly supported in single-file executables. This gives you better stack traces for debugging.

bun build cli.ts --compile --sourcemap

Before:

/tmp
❯ bun-1.1.21 build --compile --sourcemap errory.ts
[3ms] bundle 1 modules
[110ms] compile errory

/tmp
❯ ./errory
2 | // @bun
3 | // errory.ts
4 | throw new Error("hey " + n);
^
error: hey 42
at hey (/$bunfs/root/errory:4:9)
      at /$bunfs/root/errory:6:4

Bun v1.1.21 (macOS arm64)

After:

❯ bun build  --compile --sourcemap errory.ts
   [1ms]  bundle  1 modules
 [101ms] compile  errory

/tmp
❯ ./errory
1 | function hey(n: number) {
2 |   throw new Error("hey " + n);
            ^
error: hey 42
      at hey (errory.ts:2:9)
      at errory.ts:4:1

Bun v1.1.22-canary.104+c527058f1 (macOS arm64)

To reduce the memory and file-size cost of sourcemaps, we compress the input source code with zstd and serialize into a custom binary format.

Node.js compatibility

util.getSystemErrorName()

Bun now supports the util.getSystemErrorName() function from the node:util module. This allows you to return the name of the error code for an error object.

import { readFileSync } from "node:fs";
import { getSystemErrorName } from "node:util";

try {
  readFileSync("/does/not/exist");
} catch (error) {
  console.log(getSystemErrorName(error)); // "ENOENT"
}

Thanks to @nektro for implementing this feature!

util.aborted()

Bun now supports the util.aborted() function from the node:util module. This allows you to listen to an AbortSignal and receive a promise that resolves when the signal is aborted.

import { aborted } from "node:util";

const controller = new AbortController();
const promise = aborted(controller.signal);

promise.then(() => {
  console.log("Aborted!");
});

events.getEventListeners()

Bun also now supports the events.getEventListeners() function for EventTarget objects from the node:events module. This allows you to get all listeners for a given event name on a given emitter.

import { getEventListeners } from "node:events";

const emitter = new EventTarget();
emitter.addEventListener("foo", function foo() {});

console.log(getEventListeners(emitter, "foo"));
// [ [Function: foo] ]

Previously, this was only supported for EventEmitter objects from the node:events module.

NODE_EXTRA_CA_CERTS

Bun now supports the NODE_EXTRA_CA_CERTS environment variable, which allows you to specify a file containing one or more CA certificates to use when verifying TLS certificates. This works for the runtime, bun install, and everything else in Bun that makes HTTPS requests.

NODE_EXTRA_CA_CERTS=ca.pem bun install
NODE_EXTRA_CA_CERTS=another.pem bun run script.ts

Fixed: Support async interators in fs.promises.writeFile()

Node.js has undocumented support for async iterators in fs.promises.writeFile(). This was relied upon by various packages, and we decided to support it in Bun.

import { writeFile } from "node:fs/promises";

const iterator = async function* () {
  yield "Hello";
  yield "World";
};
await writeFile("hello.txt", iterator);

Fixed: Bun now exits with 1 if process.on("exit") throws

We fixed a bug where Bun would not propagate the right exit code if the exit or beforeExit event listeners threw an error.

process.on("exit", () => {
  throw new Error("Oh no!");
});

This has been fixed thanks to @nektro!

Fixed: Worker constructor would misinterpret eval property

There was a bug where the Worker constructor would misinterpret the eval property, when being explicitly defined. This was a regression that was introduced in Bun v1.1.13, and has now been fixed.

const worker = new Worker("console.log('hello!')", { eval: false });

// Before: hello!
// After: BuildMessage: ModuleNotFound

Thanks to @billywhizz for fixing this bug!

Fixed: os.freemem() now works on macOS

This was previously already implemented on Linux and Windows so this API now works on all platforms.

This has been fixed thanks to @nektro!

Web APIs

Uint8Array.toBase64()

Bun now has support for base64 encoding and decoding methods on Uint8Array. This was implemented in WebKit, which followed the TC39 stage 3 proposal for these additions.

  • Uint8Array.prototype.toBase64() converts a Uint8Array to a base64 string
  • Uint8Array.prototype.fromBase64() converts a base64 string to a Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Unit8Array.fromBase64("AQIDBA=="); // [1, 2, 3, 4, 5]

These are Web standard alternatives to ubiquitous the Buffer.prototype.toString("base64") and Buffer.from(string, "base64") patterns in Node.js.

Uint8Array.toHex()

These methods were also added for converting Uint8Array to and from hex strings.

  • Uint8Array.prototype.toHex() converts a Uint8Array to a hex string
  • Uint8Array.prototype.fromHex() converts a hex string to a Uint8Array
new Uint8Array([1, 2, 3, 4, 5]).toHex(); // "0102030405"
Unit8Array.fromHex("0102030405"); // [1, 2, 3, 4, 5]

Thanks to @dcrousso and @constellation for implementing these features in WebKit!

bun build

Converting require.main === module to import.meta.main

Previously, using require.main === module would mark the module as CommonJS (since it uses module). Now, Bun rewrites this into import.meta.main, meaning you can use this pattern alongside import statements.

import * as fs from "fs";

if (typeof require !== "undefined" && require.main === module) {
  console.log("main!", fs);
}
1 | import "fs";
           ^
error: Cannot use import statement with CommonJS-only features
    at /index.js:1:8

note: Try require("fs") instead
note: This file is CommonJS because 'module' was used

This also fixes support for require.main === module in single-file executables using CommonJS modules and bun build --compile.

Thanks to @paperdave for implementing these features!

--ignore-dce-annotations

Some JavaScript tools support special annotations that can influence during dead-code elimination. For example, the @__PURE__ annotation tells bundlers that a function call is pure (regardless of if it actually is), and that the call can be removed if it is not used.

let button = /* @__PURE__ */ React.createElement(Button, null);

Bundling or running the above code will result in an empty file, since the button variable is not used.

Sometimes, a library may include incorrect annotations, which can cause Bun to remove side effects which were needed. To workaround these issue, you can use the --ignore-dce-annotations flag when running bun build to ignore all annotations. This should only be used if dead-code elimination breaks bundles, and fixing the annotations should be preferred to leaving this flag on.

Thanks to @paperdave for implementing this feature!

Fixed: Assignment to module.exports in bundles

When bundling CommonJS modules, Bun tries to convert module.exports into exports if possible. This helps minification, but assigning to either module.exports or exports must undo optimization. This did not happen for instances of module.exports textually before the assignment, which is important for functions.

input.ts
function main() {
  console.log(module.exports);
}
module.exports = 123;
main();
out.js
 var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);

 // input.ts
 var require_input = __commonJS((exports, module) => {
   function main() {
-    console.log(exports);
+    console.log(module.exports);
   }
   module.exports = 123;
   main();
 });
 export default require_input();

Thanks to @paperdave for fixing this!

Fixed: Minifying await import("react-dom/server")

A bug causing the following error when using --minify-identifier on a package that dynamically imports react-dom has been fixed:

SyntaxError: Cannot declare an imported binding name twice

For some packages like React, Bun applies extra optimizations to convert CommonJS into ESM. In this conversion, the generated import statements had the wrong scope assigned, causing the minifier to two import statements the same minified variable name.

Fixed bugs

We spend a lot of time fixing bugs in Bun. That's because our top priority is to support Bun in production.

If you experience a bug that we haven't fixed, please file an issue or upvote an existing issue on GitHub so we can prioritize it. It helps if you are able to provide a minimal reproducible example.

Missing preview with uncaught error in Bun.serve()

When you pass development: true to Bun.serve(), we enable a builtin error handler that shows a preview of the error when an uncaught exception is thrown in the handler.

We fixed a bug where the error preview would not be shown if an uncaught exception was thrown in Bun.serve(). This was a regression that was introduced in Bun v1.1.9, and has now been fixed.

Before: Error is missing
After: Error and stack trace is shown

TextDecoder not properly decoding 192 or 193 correctly

We fixed a bug where TextDecoder would not properly decode UTF-8 sequences that started with 192 or 193.

const decoder = new TextDecoder();
const bytes = new Uint8Array([192, 191]);

console.log(decoder.decode(bytes));
// Before: "?"
// After: "\uFFFD\uFFFD"

Non-ASCII template literals with addition operator

We fixed a transpiler bug where template literals with an addition operator and non-ASCII input would not be transpiled correctly in certain cases. This was a regression that was introduced in Bun v1.1.17.

console.log(`${123}➖` + "123456 456");
// Before: 123➖
// After: 123➖123456 456

Thanks to @paperdave for fixing this bug!

Bun.serve() reliability improvements

We have made several reliability improvements to Bun.serve(). This includes:

  • No longer creating unnecessary DOMException objects when HTTP requests fail or are aborted
  • Fixed a bug where throwing an error when consuming a ReadableStream via async iterators could cause an uncatchable global error to be thrown
  • Unnecessarily abrupt TLS shutdown caused clients to not receive a WebSocket close frame in some cases
  • Serving a Bun.file() in certain cases would omit the trailing newline from the response body
  • A crash that could occur when abruptly stopping a Bun.serve() server while there are still open HTTP requests has been fixed

WebSocket server publish after close

A bug where calling .publish() on a closed ServerWebSocket would incorrectly attempt to publish a message instead of returning 0 has been fixed. In rare cases this could cause a crash.

WebSocket client receives spurious 1006 close code

We fixed a bug in the WebSocket client, where it would receive a spurious 1006 close code, even after sending the server a close frame with a different code. This was because Bun was doing a fast-shutdown, which meant that the close frame was sometimes not sent properly.

Thanks to @cirospaciari for fixing this bug!

Missing Date header when sending Bun.file() as response

We fixed a bug where the Date header was not being sent when sending a File as a response in Bun.serve(). This was because Bun uses the sendfile() syscall for sending files, and that code path was not setting the Date header.

const server = Bun.serve({
  async fetch(req) {
    const file = Bun.file("hello.txt");
    return new Response(file);
  },
});

const response = await fetch(server.url);
console.log(response.headers.get("Date"));
// Before: undefined
// After: "Wed, 06 Aug 2024 19:46:05 GMT"

Thanks to @cirospaciari for fixing this bug!

Cloned File would use the wrong filename

There was a bug where cloning a File from another file, while defining a new name, would use the wrong name. This has now been fixed.

const original = Bun.file("original.txt");
const clone = new File([original], "clone.txt");

console.log(clone.name);
// Before: "original.txt"
// After: "clone.txt"

bun upgrade on Windows with space in home directory

We fixed a bug where bun upgrade would fail on Windows if your home directory contained a space.

bun upgrade
Bun v1.1.22 is out! You're on v1.1.21
Expand-Archive : A positional parameter cannot be found that accepts argument 'PC\AppData\Local\Temp\1.1.22'.
At line:1 char:47
+ ... lyContinue';Expand-Archive -Path bun.zip C:\Users\My PC\AppDat ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [Expand-Archive], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Expand-Archive

error: Failed to verify Bun (code: FileNotFound))

If you experienced the following error, it has now been fixed, but you will have to run the PowerShell installer once more to receive the fixed build:

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

Thanks to @paperdave for fixing this issue!

UDP socket hangs on macOS 13

Bun uses an undocumented sendmsg_x() syscall as a fast-path on macOS to send multiple UDP packets at once, similar to the sendmmsg() syscall on Linux. Our usage of the API caused issues on macOS 13, which would cause UDP packets to not be sent. This has now been fixed.

Thank you to 11 contributors!