We're hiring systems engineers in San Francisco to build the future of JavaScript!
This release introduces 1st-class S3 support, HTML bundling, bun install --filter, V8 heap snapshots, bun install --lockfile-only, bun add --peer, 100% of node's path module Node.js test pass, 98% of zlib tests pass, Bun.file(path).stat(), Bun.file(path).delete(), along with many reliability improvements and bugfixes.
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
1st-class S3 support
This release introduces 1st-class S3 Object Storage support in Bun.
Modern user-facing production servers frequently choose to store files in S3-like object storage services instead of the local POSIX filesystem. Decoupling storage capacity from compute capacity prevents an entire class of production reliability issues: low disk space, high p95 response times related to file system i/o, security issues related to persistent storage.
S3 is a de-facto standard that predates ES6 by a decade. The POSIX filesystem API was never designed for the internet age (non-blocking regular files aren't a thing! It's crazy!).
Bun's S3 API is designed to be feel similar to the web's Response
and Blob
APIs (like Bun's local filesystem APIs).
import { s3, write, S3Client } from "bun";
// Bun.s3 reads environment variables for credentials
// file() returns a lazy reference to a file on S3
const metadata = s3.file("123.json");
// Download from S3
const data = await metadata.json();
const text = await metadata.text();
const arrayBuffer = await metadata.arrayBuffer();
const uint8array = await metadata.bytes();
// Upload to S3
await metadata.write(JSON.stringify({ name: "John", age: 30 }));
await metadata.write(Bun.file("./123.json"));
// Presign a URL (synchronous - no network request needed)
const url = metadata.presign({
acl: "public-read",
expiresIn: 60 * 60 * 24, // 1 day
});
// Delete the file
await metadata.delete();
We added this API to Bun because many servers don't use fs
, they use S3.
You can use Bun's S3 client with any S3-compatible object storage service, such as:
- AWS S3
- Google Cloud Storage
- DigitalOcean Spaces
- Cloudflare R2
- Backblaze B2
- MinIO
- ...and more
Learn more about Bun's S3 API in the docs.
HTML Bundler
Bun now natively supports bundling HTML files along with their associated JavaScript, CSS, and assets. This makes it easier to build static sites and web applications with Bun.
bun build --experimental-html --experimental-css ./index.html --outdir=dist
In the next version of Bun
— Jarred Sumner (@jarredsumner) December 22, 2024
bun build supports bundling assets, JS, and CSS from .html files pic.twitter.com/5VpweY1HMn
This bundles stylesheets, JavaScript, and all other assets. Multiple nested levels of JavaScript import
and require
get bundled into one file. CSS files are parsed and concatenated into one file (whether using @import
or multiple <link>
tags).
In Bun v1.2, we'll remove the --experimental-html
and --experimental-css
flags and they'll be enabled by default.
bun install --filter <pattern>
bun install
gets --filter
support. This lets you selectively install workspace packages within your monorepo.
Thanks to @dylan-conway for implementing this!
V8 Heap Snapshots
Bun now supports outputting V8 heap snapshots. This lets you use Chrome DevTools to inspect the heap of your Bun application. This wasn't previously possible before because Bun doesn't use V8 as the JavaScript engine. Bun uses JavaScriptCore. To make this work, we've added support for this in our fork of WebKit/JavaScriptCore.
Relatedly, we've also added support for v8.writeHeapSnapshot
and v8.getHeapSnapshot
in node:v8
.
import { writeHeapSnapshot, getHeapSnapshot } from "node:v8";
const path = writeHeapSnapshot();
console.log(path); // Outputs the path to the generated heap snapshot
const snapshot = await Bun.file(path).json();
console.log(snapshot); // Outputs the parsed heap snapshot data
In the next version of Bun
— Jarred Sumner (@jarredsumner) January 2, 2025
Bun.generateHeapSnapshot("v8") outputs V8's .heapsnapshot format, so you can use Chrome DevTools and VSCode to debug memory usage pic.twitter.com/veIBqASLoi
Errors show more properties
Previously, Bun hardcoded a small list of properties to display when printing top-level errors. Now we print all the own properties of the error.
Fixed:
❯ bun such.js
1 | const err = new Error("An example error");
^
error: An example error
wow: "wow",
such: "property",
very: "information",
at <file>.js:1:13
Previously, Bun would not show properties like wow
, such
, or very
when displaying errors. Now it shows everything defined on the error object directly.
❯ bun-1.1.42 such.js
1 | const err = new Error("An example error");
^
error: An example error
at <file>.js:1:13
bun run
-F
is for --filter
bun run --filter='*pkg' <script>
now supports the -F
shorthand
bun run -F=<pattern> <script>
bun run --filter=<pattern> <script>
bun -F <pattern> <script>
bun -F=<pattern> <script>
--elide-lines=N
controls filter output line length
The --elide-lines
option in bun run --filter
controls the number of output lines displayed when interactively running scripts in a terminal. This helps avoid missing errors in long scripts, while still showing the output of the script.
bun run --filter ./packages/dep0 --elide-lines=5 script
Thanks to @martinamps for implementing this!
pnpx
and pnpm dlx
auto-replacement
When you use bun run
to run package.json scripts, we swap out yarn run
, npm run
, and pnpm run
with bun run
(so that you don't have to update your package.json). Now we also replace pnpx
and pnpm dlx
with bunx
.
Thanks to @pfgithub for implementing this!
bun install
New: slow postinstall script logging
Buggy postinstall scripts on npm can cause your install to hang.
To help identify the package causing the hang, bun install
now logs the package name of any postinstall scripts that takes longer than 30 seconds to run.
If you run bun install
with --verbose
, it will log the package name every 2 seconds instead of every 30 seconds.
New: bundle(d)Dependencies
in bun install
Bun now supports handling bundle(d)Dependencies
in the bun install
command. This update allows users to specify dependencies that should be bundled together, streamlining package management. This makes it easier for users to create consistent and manageable projects, especially when dealing with multiple dependencies.
Thanks to @dylan-conway for implementing this!
New: --lockfile-only
option for bun install
You can now use the --lockfile-only
option with the bun install
command to generate a lockfile without installing any dependencies. This is useful when you want to update or create a lockfile based on your package.json
without affecting the state of your node_modules
directory.
Thanks to @dylan-conway for implementing this!
New: --peer
option in bun add
You can now add a package as a peer dependency using the --peer
option with the bun add
command. This functionality allows users to specify peer dependencies directly from their terminal, enhancing the usability of package management in Bun.
bun add --peer @types/bun
Thanks to @riskymh for implementing this!
Fixed: bun.lock file permissions
Bun's text-based lockfile was being marked as world-writable and executable, which was unnecessary.
This has been fixed, thanks to @dylan-conway!
Fixed: install.cache.dir
in bunfig.toml
The install.cache.dir
option now correctly reads the cache directory from your bunfig.toml
. This change ensures that the cache directory can be customized through your configuration.
[install]
cache.dir = "./your_custom_cache_directory"
Thanks to @chawyehsu for implementing this!
Fixed: crash in bun patch --commit
A crash in bun patch --commit
has been fixed, thanks to @dylan-conway!
Fixed: quote-escaping in bun.lock
package names
Invalid "name"
fields in package.json files were being persisted in the bun.lock
file without escaping the quote character. This has been fixed, thanks to @dylan-conway!
Node.js compatibility improvements
100% of node:path
tests now pass
Bun's node:path
module now passes 100% of Node.js' test suite.
Fixed: potential crash when joining long win32 paths in node:path
A crash that could occur when joining very long paths using the node:path
module has been fixed, thanks to @DonIsaac!
98.08% of node:zlib
tests now pass
All but 1 test in the node:zlib
module now pass.
Thanks to @nektro for this!
Fixed: memory leak in node:zlib
A memory leak in the node:zlib
module has been fixed. This particularly impacted Brotli compression.
Fixed: zlib dictionary option handling
The dictionary
option in zlib
now works as expected. This fixed an issue impacting some Minecraft servers using Bun.
const deflate = zlib.createDeflate({ dictionary: Buffer.from("...") });
Fixed: napi_set_last_error method in Node-API
The napi_set_last_error
method in Node-API now sets an appropriate error code prior to returning. Previously, we were not consistently setting the error code prior to returning from Node-API functions - which caused some native addons to not receive the correct error code after a failed function call.
Thanks to @190n for implementing this!
Fixed: process.kill
allows zero or negative PIDs
An incorrect validation was preventing process.kill
from accepting negative PIDs. Negative PIDs let you kill an entire process group instead of just a single process.
const { spawn, exec } = require("child_process");
// Starting a process
const child = spawn("some-command");
// Killing the process using a negative PID
process.kill(-child.pid);
Thanks to @nektro for implementing this!
--expose-gc
flag adds globalThis.gc
Bun now implements the Node.js --expose-gc
CLI flag, alongside the Bun.gc()
method. This CLI flag adds gc
to the global scope, which allows you to manually trigger garbage collection in your code.
bun --expose-gc <script.js>
The Bun.gc()
method is the same as globalThis.gc
.
Fixed: process.memoryUsage().arrayBuffers
Previously, process.memoryUsage().arrayBuffers
was always reporting 0
. Now we do our best to report the correct number.
What's interesting about this is a difference in how JavaScriptCore and V8 handle ArrayBuffer
memory. In JavaScriptCore, typed array's buffers are "owned" by the TypedArray type (a Uint8Array
and not an ArrayBuffer
, for example). They don't become "shared" between Uint8Array
and ArrayBuffer
(or other typed arrays) until either:
- The
.buffer
property was accessed (creating theArrayBuffer
). - The
.subarray
method was called (creating a shared reference). - The typed array size is > 512 bytes.
- The typed array was externally allocated outside of JavaScript (such as when reading a file in Bun)
When the ArrayBuffer's contents are not owned by JavaScriptCore, they aren't included in the process.memoryUsage().arrayBuffers
statistic, and instead are accounted for in the process.memoryUsage().external
statistic.
Improved: module.findSourceMap
require("module").findSourceMap
no longer throws an error when called. Previously, this threw an error saying we don't implement module.SourceMap
yet. It's still not implemented, but throwing an error caused issues with Next.js in certain cases - so now we return undefined
instead, which matches Node.js' behavior when no source map is found.
bun build
Improved stack overflow detection
Bun's JavaScript parser now proactively checks if it's about to run out of stack space, and throws an artifical stack overflow error.
Fixed (now a recoverable error):
❯ bun lots-of-for-loop.js
286 | for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++) for (let i = 0; i < 1; i++)
^
error: Maximum call stack size exceeded
at lots-of-for-loop.js:286:49
Previously, bun would crash with a hard SIGILL, which is not recoverable.
bun: killed SIGILL (core dumped)
Fixed: large number of if statements led to stack overflow
Two things caused stack overflows with large numbers of nested if statements:
The statement parsing step for
if
statements was always recursive, even if theif
statement was not nested. This recursion used too much stack space. Now it's no longer recursive.The statement visiting step in Bun's JavaScript parser was originally a ~1,300 line function. This used several kilobytes of stack space, which became a problem when recursing through heavily nested if/for loops. We split it into more functions and avoided recursion more frequently.
Fixed: #bun
pragma
Ordinarily, bun transpiles every file at runtime. To avoid potentially transpiling the same file multiple times, bun looks for a // @bun
pragma comment.
There was a bug where Bun's JavaScript lexer would sometimes interpret the string #bun
present in comments as the bun
pragma meant for disabling transpilation.
This has been fixed, thanks to @DonIsaac!
Fixed: cross-compile for musl in bun build --compile
The code that parses the --target
flag in bun build --compile
wasn't updated for when we added musl support in Bun v1.1.40. This has been fixed, thanks to @thecrypticace.
Fixed: sourcemap comments CSS files
The //# sourceMappingURL
comment meant for JavaScript files was being printed in CSS files. This has been fixed.
Fixed: crash when parsing malformed TypeScript enums
A crash that occurred when handling malformed enums in the transpiler has been fixed.
// Example of a malformed enum that will generate a parse error
enum Foo { [2]: 'hi' }
Thanks to @paperclover for implementing this!
Runtime improvements
Override "Content-Length"
and Transfer-Encoding
in Bun.serve()
Bun.serve now supports overriding the "Content-Length"
and Transfer-Encoding
headers.
This is particularly useful for HEAD
requests, where you want to return a Content-Length
but not actually send the body.
const app = Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello World", {
headers: { "Content-Length": "7" },
});
},
});
Thanks to @cirospaciari for implementing this!
New: stat
method in Bun.file(path)
You can now use stat
without node:fs
in Bun.
import { file } from "bun";
await file("./path-to-file.txt").stat();
This returns useful information about the file, such as its size, type, and last modified date. This returns the same type as in node:fs
's stat
function.
New: delete
method in Bun.file(path)
You can now delete files in Bun, without node:fs
.
import { file } from "bun";
await file("./path-to-file.txt").delete();
More accurate heap snapshots
The memory cost of adding entries to performance.mark
and performance.measure
is now reported accurately in heap snapshots.
Fixes to stack traces with eval and node:vm
Ordinarily, Bun transpiles every file at runtime. This also means we have to sourcemap stack traces and error messages.
We don't transpile code for node:vm or for eval
(it would break compatibility). However, in certain cases, when some of the stack frames in a stack trace were from eval
or node:vm
and some of them were from Bun, the trace could get incorrectly sourcemapped.
Also, the //# sourceURL
comment was not always being respecteed in stack traces, which is particularly unhelpful for eval
and node:vm
where the sourceURL can be passed in (via comment for eval
, and via options in node:vm
).
This has been fixed.
In the next version of Bun
— Jarred Sumner (@jarredsumner) January 7, 2025
Several bugs involving stack traces and line numbers when using eval, node:vm, or //# sourceURL= URLs are fixed pic.twitter.com/ENkRaGgeFH
Fixed: stack overflow in console.log
A stack overflow causing an unrecoverable crash that could occur in console.log
has been fixed.
Now, console.log
proactively checks for how much stack space remains, and throws a StackOverflowError
if it's about to run out.
Along the way, we've also fixed several cases where Bun would use large amounts of stack space for file path buffers on Windows (Windows file paths go up to 64 KB)
Fixed: memory leak in WebSocket
client
A memory leak impacting the WebSocket client has been fixed.
In the next verison of Bun
— Jarred Sumner (@jarredsumner) December 31, 2024
A memory leak impacting the WebSocket client has been fixed.
left: new
right: previously pic.twitter.com/XfxpmksZji
remove
and isRemoved
in HTMLRewriter's DocType
Bun's HTMLRewriter
implementation now includes remove()
and isRemoved()
methods. The remove()
method allows users to delete the <!DOCTYPE>
declaration from the HTML document during transformation, and the isRemoved()
property is a boolean that tells you if the <!DOCTYPE>
has been removed.
const html = "<!DOCTYPE html><html><head></head><body>Hello</body></html>";
const rewriter = new HTMLRewriter().onDocument({
doctype(doctype) {
doctype.remove();
console.log(doctype.removed); // Logs: true
},
});
const result = rewriter.transform(html);
console.log(result); // Logs: "<html><head></head><body>Hello</body></html>"
Thanks to @kimberly for implementing this!
Fixed: formatting Set
in Bun.inspect
The formatting of Set
objects in the Bun.inspect()
function has been corrected. This ensures that when a Set
is passed to Bun.inspect()
, it will now display its contents properly formatted.
import { it, expect } from "bun:test";
it("Set is propperly formatted in Bun.inspect()", () => {
const set = new Set(["foo", "bar"]);
const formatted = Bun.inspect({ set });
expect(formatted).toBe(`{
set: Set(2) {
"foo",
"bar",
},
}`);
});
Thanks to @laesse for implementing this!
Fixed: socket close behavior
In rare cases, the close
callback for a socket could be called after the end
callback. This has been fixed.
Fixed: Theroetical crash when iterating over properties
A bug in our JavaScriptCore <-> Zig bindings could lead to a crash when native code iterated through properties of an object in rare cases. This has been fixed.
Fixed: export default <file-path>
in error messages
A bug that could cause bun to display "export default '<file-path>'" as the source code for an error message has been fixed.
Bun.sql is almost ready
Over the last year, one of my side projects has been a fast, builtin PostgreSQL client for Bun, and it's almost ready.
import { sql } from "bun";
import { expect } from "bun:test";
const result = await sql`select 1 as x`;
expect(result).toEqual([{ x: 1 }]);
Bun.sql will be released in Bun v1.2 - coming in 13 days at the time of writing. You can try it in the canary build of Bun.
Here's what's new for Bun.sql
in this release.
Fixed: duplicate column names & numeric column names
A crash that occurred when duplicate column names were returned in query results has been fixed.
const result = await sql`select 1 as x, 2 as x, 3 as x`;
expect(result).toEqual([{ x: 3 }]);
const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 0 as "0"`;
expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, "0": 0 }]);
Previously, we also wouldn't properly support numeric column names.
Fixed: JSONB support
Bun's builtin PostgreSQL client now supports the jsonb
data type.
import { sql } from "bun";
import { expect } from "bun:test";
test("jsonb support", async () => {
const result = await sql`select '{"foo": "bar"}'::jsonb as x`;
expect(result).toEqual([{ x: { foo: "bar" } }]);
});
onopen
and onclose
callbacks
The onopen
and onclose
tell you when the database connection opened or closed.
import { SQL } from "bun";
const sql = new SQL({
url: "postgres://localhost:5432/mydb",
onopen: () => console.log("Connection opened"),
onclose: () => console.log("Connection closed"),
});
const [{ x }] = await sql`select 1 as x`;
console.log({ x });
await sql.close();
console.log("closed");
This logs:
Connection opened
{ x: 1 }
Connection closed
closed
Thanks to @dylan-conway for this addition!
Error codes for PostgreSQL errors
The following error codes are now available for PostgreSQL errors.
"ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2"
"ERR_POSTGRES_CONNECTION_CLOSED"
"ERR_POSTGRES_CONNECTION_TIMEOUT"
"ERR_POSTGRES_EXPECTED_REQUEST"
"ERR_POSTGRES_EXPECTED_STATEMENT"
"ERR_POSTGRES_IDLE_TIMEOUT"
"ERR_POSTGRES_INVALID_BACKEND_KEY_DATA"
"ERR_POSTGRES_INVALID_BINARY_DATA"
"ERR_POSTGRES_INVALID_BYTE_SEQUENCE"
"ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING"
"ERR_POSTGRES_INVALID_CHARACTER"
"ERR_POSTGRES_INVALID_MESSAGE"
"ERR_POSTGRES_INVALID_MESSAGE_LENGTH"
"ERR_POSTGRES_INVALID_QUERY_BINDING"
"ERR_POSTGRES_INVALID_SERVER_KEY"
"ERR_POSTGRES_INVALID_SERVER_SIGNATURE"
"ERR_POSTGRES_LIFETIME_TIMEOUT"
"ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET"
"ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET"
"ERR_POSTGRES_OVERFLOW"
"ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64"
"ERR_POSTGRES_SASL_SIGNATURE_MISMATCH"
"ERR_POSTGRES_SERVER_ERROR"
"ERR_POSTGRES_SYNTAX_ERROR"
"ERR_POSTGRES_TLS_NOT_AVAILABLE"
"ERR_POSTGRES_TLS_UPGRADE_FAILED"
"ERR_POSTGRES_UNEXPECTED_MESSAGE"
"ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD"
"ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD"
"ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT"
"ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE"
Other changes
- The list of completions used for
bun add
is now compressed, which saves about 100 KB. - We rewrote the code that checks for unicode identifiers which shaved off about 250 KB.