We're hiring systems engineers in San Francisco to build the future of JavaScript!
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
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
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:
bun install --save-text-lockfile
We wrote a blog post about bun.lock
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 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.
In the next version of Bun
— Bun (@bunjavascript) December 5, 2024
fetch()'s body supports streaming ReadableStream, Node.js streams and async generator* functions pic.twitter.com/s6V2ugfEkK
Thanks to @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 tonet.connect
signals, Bun was callingdestroy
on thenet.Socket
instead of emitting anabort
event. This has been fixed, thanks to @cirospaciari! - Calling
pause
on anet.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 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 @paperdave!
100% of Node's punycode
tests pass
Bun is now fully compatible with Node.js punycode implementation.
Thanks to @snoglobe!
100% of Node's querystring
tests pass
We fixed some edgecases in the "querystring"
module to align with Node.js.
Thanks to @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!
Improved node:os test coverage
Only one test failure remains in the os
module.
Fixed: crypto.createHash(alg, options)
missing option
Thanks to @heimskr!
Fixed: napi_wrap
The napi_wrap
function now behaves the same as in Node.js.
Thanks to @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!
Fixed: fs.readFileSync
with flag
parameter in object
The following would incorrectly ignore the flag
parameter:
import fs from "fs";
fs.readFileSync("data.txt", { encoding: "utf8", flag: "w+" });
This has been fixed, thanks to @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.
bun add -g @napi-rs/cli
napi new
cargo add bun-native-plugin
From there, you can implement the onBeforeParse
hook:
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()
:
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 for implementing this!
CSS parser improvements
Several CSS parser and printer edge cases have been fixed, thanks to @zackradisic!
Inject environment variables in Bun.build()
This release adds support for injecting environment variables in Bun.build()
and bun build
:
// 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",
});
bun build ./app.tsx --env='PUBLIC_*'
bun build ./app.tsx --env=inline
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
.
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:
bun test -u
We've also added:
expect().toThrowErrorMatchingSnapshot()
expect().toThrowErrorMatchingInlineSnapshot()
Thanks to @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
, andResponse
now report the size including their body, URL, and underlying structs instead of only the JavaScript wrapper object sizes.URLSearchParams
andHeaders
now report the size of size of the C++ classes along with the size of each string.ServerWebSocket
andWebSocket
now reports the size of the underlying structs and buffered dataFormData
'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 dataBun.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.
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%.
Microbenchmark
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.
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.
Faster String.prototype.at
In the next version of Bun & Safari
— Bun (@bunjavascript) December 12, 2024
"foo".at(i) gets 44% faster, thanks to @__sosukesuzuki pic.twitter.com/UtkkJSp6Vb
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
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 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!
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!