We're hiring systems engineers in San Francisco to build the future of JavaScript!
This release fixes 32 bugs. S3 storage class support, X25519 support in crypto.generateKeyPair. fs.stat uses less memory. node:fs, node:child_process, and node:process compatibility improvements. CSS parser bugfixes. Zero-filled buffers with --zero-fill-buffers
flag. Indented inline snapshots in bun test. Improved: String GC Reporting Accuracy. Fixed memory leaks in Bun.serve(), Bun.pathToFileURL, and with some strings. Disable minification in HTML imports.
To install Bun
curl -fsSL https://bun.sh/install | bash
npm install -g bun
powershell -c "irm bun.sh/install.ps1|iex"
scoop install 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
S3 storage class support
You can now pass a storageClass
option to Bun's builtin S3 client.
This lets you configure how S3 object storage stores your objects, so that you can optimize for cost or latency.
it("should work with storage class", async () => {
const s3file = s3("s3://bucket/credentials-test", s3Options);
const url = s3file.presign({
expiresIn: 10,
storageClass: "GLACIER_IR",
});
expect(url).toBeDefined();
expect(url.includes("X-Amz-Expires=10")).toBe(true);
expect(url.includes("x-amz-storage-class=GLACIER_IR")).toBe(true);
expect(url.includes("X-Amz-Date")).toBe(true);
expect(url.includes("X-Amz-Signature")).toBe(true);
expect(url.includes("X-Amz-Credential")).toBe(true);
expect(url.includes("X-Amz-Algorithm")).toBe(true);
expect(url.includes("X-Amz-SignedHeaders")).toBe(true);
});
X25519 support in crypto.generateKeyPair
The crypto.generateKeyPair
API now supports the X25519 curve for key generation and Diffie-Hellman key exchange.
import crypto from "node:crypto";
// Generate Alice's keys
const alice = crypto.generateKeyPairSync("x25519", {
publicKeyEncoding: {
type: "spki",
format: "der",
},
privateKeyEncoding: {
type: "pkcs8",
format: "der",
},
});
// Generate Bob's keys
const bob = crypto.generateKeyPairSync("x25519", {
publicKeyEncoding: {
type: "spki",
format: "der",
},
privateKeyEncoding: {
type: "pkcs8",
format: "der",
},
});
// Convert keys for DH computation
const alicePrivateKey = crypto.createPrivateKey({
key: alice.privateKey,
format: "der",
type: "pkcs8",
});
const bobPublicKey = crypto.createPublicKey({
key: bob.publicKey,
format: "der",
type: "spki",
});
Previously, this would throw a DOMException
:
NotSupportedError: The operation is not supported.
Indented inline snapshots in bun test
Jest-style inline snapshots in tests now support indentation, making test files more readable. The indentation is automatically detected and preserved when updating snapshots.
test("my test", () => {
expect(value).toMatchInlineSnapshot(`
{
"foo": "bar",
"baz": 123
}
`);
});
Thanks to @pfgithub for the fix!
Improved: String GC Reporting Accuracy
In WebKit (and Bun), strings are reference-counted. JavaScript strings (which are garbage collected) hold a reference to the reference-counted string. The garbage collector reports the memory usage of the reference-counted string as the byte length of the string divided by the reference count:
inline size_t StringImpl::costDuringGC()
{
if (isStatic())
return 0;
if (bufferOwnership() == BufferSubstring)
return divideRoundedUp(substringBuffer()->costDuringGC(), refCount());
size_t result = m_length;
if (!is8Bit())
result <<= 1;
return divideRoundedUp(result, refCount());
}
Previously, Bun's JavaScriptCore bindings would typically have a pattern like this for creating a string for JavaScript to consume:
fn myFunction(globalObject: *JSC.JSGlobalObject, input: []const u8) JSC.JSValue {
// Reference count: 1
const string = bun.String.createUTF8(input);
// - Reference count: 1 (after the function returns)
defer string.deref();
// + Reference count: 2 (before the function returns)
return string.toJS(globalObject);
}
So, if you used a function like fs.readFileSync(path, "utf-8")
to read a file containing 64 KB of ASCII data, the memory usage would be reported to JavaScriptCore's garbage collector as 32 KB instead of 64 KB.
We've updated our bindings to use a different pattern which combines creating the string for JavaScript in a single step, so that the reference count is not incremented an extra time before the JavaScript string is returned.
fn myFunction(globalObject: *JSC.JSGlobalObject, input: []const u8) JSC.JSValue {
// Reference count: 1
return bun.String.createUTF8ForJS(globalObject, input);
}
Will this reduce memory usage?
Probably sometimes.
Disable minification in HTML imports
You can now disable minification for HTML imports by configuring the minify
option in bunfig.toml
:
[serve.static]
plugins = ["bun-plugin-tailwind"]
minify = false
# or
# minify.whitespace = false
# minify.identifiers = false
# minify.syntax = false
If you run into any issues that only occur when development: false
, try disabling minification.
If unspecified:
- When you pass
development: true
to Bun.serve(),minify
isfalse
- When you pass
development: false
to Bun.serve(),minify
istrue
Minification helps in production when you want to have optimized builds with less code to send to the client.
We will expose the rest of the options from Bun.build
in the near future.
Zero-filled buffers with --zero-fill-buffers
flag
Bun now supports Node.js's --zero-fill-buffers
flag, which forces all newly allocated buffers to be zero-filled for improved security. When enabled, methods like Buffer.allocUnsafe()
will always return zero-filled buffers.
// Run with: bun --zero-fill-buffers script.js
const buf = Buffer.allocUnsafe(20);
console.log(buf); // Will contain all zeros instead of arbitrary memory
Thanks to @nektro!
node:process compatibility improvements
- A bug causing
process.stdin.ref
andprocess.stdin.unref
to beundefined
when it shouldn't have been was fixed. - A bug that could cause
process.stdout.write
in rare cases to not prevent the process from exiting was fixed.
Together, these fix a regression impacting ink
.
node:fs compatibility improvements
This release fixes several compatibility issues with:
fs.{s,f}statSync
fs.promises.{s,f}stat
fs.promises.{s,f}statSync
fs.Stats
The fs.Stats
constructor is now consistent with Node.js' behavior.
The following diff;
const fs = require("fs");
const stats = new fs.Stats();
console.log(stats);
Before vs now:
Stats {
atime: 1970-01-01T00:00:00.000Z,
atime: Invalid Date,
atimeMs: 0,
atimeMs: undefined,
birthtime: 1970-01-01T00:00:00.000Z,
birthtime: Invalid Date,
birthtimeMs: 0,
birthtimeMs: undefined,
blksize: 0,
blksize: undefined,
blocks: 0,
blocks: undefined,
ctime: 1970-01-01T00:00:00.000Z,
ctime: Invalid Date,
ctimeMs: 0,
ctimeMs: undefined,
dev: 0,
dev: undefined,
gid: 0,
gid: undefined,
ino: 0,
ino: undefined,
isBlockDevice: [Function: isBlockDevice],
isCharacterDevice: [Function: isCharacterDevice],
isDirectory: [Function: isDirectory],
isFIFO: [Function: isFIFO],
isFile: [Function: isFile],
isSocket: [Function: isSocket],
isSymbolicLink: [Function: isSymbolicLink],
mode: 0,
mode: undefined,
mtime: 1970-01-01T00:00:00.000Z,
mtime: Invalid Date,
mtimeMs: 0,
mtimeMs: undefined,
nlink: 0,
nlink: undefined,
rdev: 0,
rdev: undefined,
size: 0,
size: undefined,
uid: 0,
uid: undefined,
}
Node.js:
Stats {
dev: undefined,
mode: undefined,
nlink: undefined,
uid: undefined,
gid: undefined,
rdev: undefined,
blksize: undefined,
ino: undefined,
size: undefined,
blocks: undefined,
atimeMs: undefined,
mtimeMs: undefined,
ctimeMs: undefined,
birthtimeMs: undefined,
atime: Invalid Date,
mtime: Invalid Date,
ctime: Invalid Date,
birthtime: Invalid Date
}
fs.fstatSync bigint support
Previously, the fs.fstatSync
method ignored the bigint
option.
const fs = require("fs");
const stats = fs.fstatSync(0, { bigint: true });
console.log(stats);
Now it supports it.
BigIntStats {
dev: 0n,
ino: 15807n,
mode: 8592n,
nlink: 1n,
uid: 501n,
gid: 4n,
rdev: 268435812n,
size: 0n,
blksize: 65536n,
blocks: 0n,
atimeMs: 1737726280078n,
mtimeMs: 1737726280452n,
ctimeMs: 1737726280452n,
birthtimeMs: 0n,
atimeNs: 1737726280078692000n,
mtimeNs: 1737726280452662000n,
ctimeNs: 1737726280452662000n,
birthtimeNs: 0n,
atime: 2025-01-24T13:44:40.078Z,
mtime: 2025-01-24T13:44:40.452Z,
ctime: 2025-01-24T13:44:40.452Z,
birthtime: 1970-01-01T00:00:00.000Z,
}
EINTR
There were a few cases where Bun was not handling EINTR in fs.writeFile
and fs.readFile
.
These have been fixed. EINTR can interrupt system calls like read()
, write()
, truncate()
, ftruncate()
, fchmod()
, and more. The correct behavior is to retry the system call on interruption.
Stat uses less memory
In the next version of Bun
— Jarred Sumner (@jarredsumner) January 24, 2025
fs.stat uses less memory, passes more node.js tests and is a little faster pic.twitter.com/Zx8FSwN6jP
node:child_process bugfix
A race condition involving multiple sockets as extra file descriptors closing before the handle was associated with the subprocess in child_process.spawn
was fixed, thanks to @anaisbetts.
Worker reliability improvements
We made several reliability improvements to Bun's Worker
implementation. Worker continues to be experimental (as noted in the documentation), but it's now more reliable.
- A case where events like "close" would not be emitted was fixed.
- A case where
terminate
's Promise would not resolve was fixed.
More bugfixes
bun run
no longer tries to execute JSON files
Previously, if you did bun .
and there was an index.json
file, Bun would attempt to execute it. Now, Bun won't do that.
Thanks to @pfgithub for the fix!
Fixed: fs.Dir.close regression
A regression in fs.Dir.close was fixed, thanks to @DonIsaac.
Buffer.prototype.toLocaleString === Buffer.prototype.toString
Buffer.prototype.toLocaleString is now an alias for Buffer.prototype.toString, matching Node.js behavior.
const buf = Buffer.from("Hello world");
console.log(buf.toString === buf.toLocaleString); // true
Fixed: dgram socket address/port reuse
The reuseAddr
and reusePort
options in UDP sockets now work more reliably across platforms. On Linux, reusePort
enables load balancing between multiple processes. On other platforms, reuseAddr
allows reusing an address that is already in use.
import { createSocket } from "node:dgram";
// Enable address reuse
const socket = createSocket({
reuseAddr: true, // Allow reusing address
reusePort: true, // Enable load balancing on Linux
});
socket.bind(8000);
Thanks to @heimskr!
Fixed: Symbol() formatting in Bun.inspect
Empty Symbols and Symbols with empty descriptions are now correctly formatted with parentheses when using Bun.inspect()
or console.log()
.
console.log(Symbol()); // Symbol()
console.log(Symbol("")); // Symbol()
Fixed: memory leak in Bun.pathToFileURL
A memory leak in Bun.pathToFileURL
was fixed. This was caused by incrementing a reference count too many times.
Fixed: memory leak in Bun.serve() impacting some requests
In some cases, a memory leak could occur for requests with certain URLs. This was fixed. This was caused by not decrementing a reference count in one code path.
Fixed: memory leak regression with some strings in Bun v1.2
An issue with Bun's JavaScriptCore <> Zig bindings led to certain strings having a reference count that was not decremented. This was fixed.
Fixed: AbortSignal GC'ing too early
A bug where AbortSignal
could be garbage collected too early was fixed. This occurred when passing to fetch
or Bun.spawn
and not having an "abort"
event listener attached. This bug could cause the "abort"
event to not be emitted.