Bun v1.0.21 fixes 33 bugs (addressing 80 👍 reactions). console.table() support. Bun.write, Bun.file, and bun:sqlite use less memory. Large file uploads with FormData use less memory. bun:sqlite error messages get more detailed. Memory leak in errors from node:fs fixed. Node.js compatibility improvements, and many crashes fixed
Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager — all in one. In case you missed it, here are some of the recent changes to Bun:
v1.0.18- Fixes 27 bugs (addressing 28 👍 reactions). A hang impacting create-vite & create-next & stdin has been fixed. Lifecycle scripts reporting "node" or "node-gyp" not found has been fixed. expect().rejects works like Jest now, and more bug fixesv1.0.19- Fixes 26 bugs (addressing 92 👍 reactions). Use @types/bun instead of bun-types. Fixes --frozen-lockfile bug. bcrypt & argon2 packages now work. setTimeout & setInterval get 4x higher throughput. module mocks in bun:test resolve specifiers. Optimized spawnSync() for large stdio on Linux. Bun.peek() gets 90x faster, expect(map1).toEqual(map2) gets 100x faster. Bugfixes to NAPI, bun install, and Node.js compatibility improvementsv1.0.20- Reduces memory usage infs.readlink,fs.readFile,fs.writeFile,fs.statandHTMLRewriter. Fixes a regression where setTimeout caused high CPU usage on Linux.HTMLRewriter.transformnow supports strings andArrayBuffer.fs.writeFile()andfs.readFile()now supporthex&base64encodings.Bun.spawnshows how much CPU & memory the process used.
To install Bun:
curl -fsSL https://bun.sh/install | bashnpm install -g bunbrew tap oven-sh/bunbrew install bundocker pull oven/bundocker run --rm --init --ulimit memlock=-1:-1 oven/bunTo upgrade Bun:
bun upgradeconsole.table() is now supported
console.table() is a Web API that prints a table from an object or iterable. It's useful for debugging.
const { headers } = await fetch("https://example.com");
console.table(headers)
In this example, Bun will print an ASCII table of the response headers.
┌────┬──────────────────┬───────────────────────────────┐
│ │ 0 │ 1 │
├────┼──────────────────┼───────────────────────────────┤
│ 0 │ age │ 519517 │
│ 1 │ cache-control │ max-age=604800 │
│ 2 │ content-encoding │ gzip │
│ 3 │ content-length │ 648 │
│ 4 │ content-type │ text/html; charset=UTF-8 │
│ 5 │ date │ Tue, 02 Jan 2024 02:37:00 GMT │
│ 6 │ etag │ "3147526947+gzip" │
│ 7 │ expires │ Tue, 09 Jan 2024 02:37:00 GMT │
│ 8 │ last-modified │ Thu, 17 Oct 2019 07:18:26 GMT │
│ 9 │ server │ ECS (sac/2508) │
│ 10 │ vary │ Accept-Encoding │
│ 11 │ x-cache │ HIT │
└────┴──────────────────┴───────────────────────────────┘
Thanks to @otgerrogla for implementing this feature!
You can also use console.table with a variety of different objects, including:
Arrays with console.table()
const foo = await fetch("https://api.github.com/repos/elyisajs/elysia/issues");
console.table(await issues.json(), ["title", "status"]);
Objects with console.table()
const user = await fetch("https://api.github.com/users/ThePrimeagen");
console.table(await user.json());
This prints something like:
┌─────────────────────┬──────────────────────────────────────────────────────────────────┐
│ │ Values │
├─────────────────────┼──────────────────────────────────────────────────────────────────┤
│ login │ ThePrimeagen │
│ id │ 4458174 │
│ node_id │ MDQ6VXNlcjQ0NTgxNzQ= │
│ avatar_url │ https://avatars.githubusercontent.com/u/4458174?v=4 │
│ gravatar_id │ │
│ url │ https://api.github.com/users/ThePrimeagen │
│ html_url │ https://github.com/ThePrimeagen │
│ followers_url │ https://api.github.com/users/ThePrimeagen/followers │
│ following_url │ https://api.github.com/users/ThePrimeagen/following{/other_user} │
│ gists_url │ https://api.github.com/users/ThePrimeagen/gists{/gist_id} │
│ starred_url │ https://api.github.com/users/ThePrimeagen/starred{/owner}{/repo} │
│ subscriptions_url │ https://api.github.com/users/ThePrimeagen/subscriptions │
│ organizations_url │ https://api.github.com/users/ThePrimeagen/orgs │
│ repos_url │ https://api.github.com/users/ThePrimeagen/repos │
│ events_url │ https://api.github.com/users/ThePrimeagen/events{/privacy} │
│ received_events_url │ https://api.github.com/users/ThePrimeagen/received_events │
│ type │ User │
│ site_admin │ false │
│ name │ ThePrimeagen │
│ company │ CEO Of TheStartup │
│ blog │ http://twitch.tv/ThePrimeagen │
│ location │ 9th Ring, Vim │
│ email │ null │
│ hireable │ null │
│ bio │ null │
│ twitter_username │ ThePrimeagen │
│ public_repos │ 199 │
│ public_gists │ 0 │
│ followers │ 23186 │
│ following │ 3 │
│ created_at │ 2013-05-17T15:05:59Z │
│ updated_at │ 2023-11-22T19:47:49Z │
└─────────────────────┴──────────────────────────────────────────────────────────────────┘
bun:sqlite has more detailed error messages
Previously, bun:sqlite might throw an error like:
7 | db.exec('INSERT INTO foo VALUES ("hello")');
^
error: constraint failed
at run (bun:sqlite:185:11)
at /private/tmp/sqlite.js:7:1
But now, you'll get a more detailed error like:
7 | db.exec('INSERT INTO foo VALUES ("hello")');
^
SQLiteError: UNIQUE constraint failed: foo.bar
errno: 2067
code: "SQLITE_CONSTRAINT_UNIQUE"
at run (bun:sqlite:185:11)
at /private/tmp/fetch.js:7:1
What changed?
error.nameis nowSQLiteErrorinstead ofErrorerror.messagenow uses the extended error message from SQLite which frequently includes the table and column nameerror.errnois the extended error code from SQLiteerror.codeis the extended error code from SQLite as a stringerror.byteOffsetreports the byte offset in the SQL statement where the error occurred, if available
+ SQLiteError: UNIQUE constraint failed: foo.bar
- error: constraint failed
+ errno: 2067
+ code: "SQLITE_CONSTRAINT_UNIQUE"
at run (bun:sqlite:185:11)
at /private/tmp/sqlite.js:7:1
bun:sqlite uses less memory
bun:sqlite now reports SQLite's memory usage to the garbage collector, which prompts the garbage collector to free memory more aggressively when necessary.
This example, which creates 100,000 prepared statements, consumes 4x less memory:
| Memory usage | Bun version |
|---|---|
| 71 MB | Bun v1.0.21 |
| 287 MB | Bun v1.0.20 |
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, content TEXT)");
db.exec('INSERT INTO test VALUES (NULL, "hello")');
for (let i = 0; i < 100_000; i++) {
db.prepare(`SELECT content as content${i} FROM test`);
}
console.log((process.memoryUsage.rss() / 1024 / 1024) | 0, "MB");
We've also fixed a bug in the bindings implementation that caused the internal SQLStatement object to not call the destructor for the prepared statement when it was garbage collected.
Fixed: Crash in bun:sqlite with latin1 characters
There was a crash in bun:sqlite when using latin1 supplemental characters in a query, which has now been fixed.
const db = new Database(":memory:");
db.run(
"CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT, copyright© TEXT)",
);
The © symbol is a valid latin1 supplemental character, and previously the code for sending the query to SQLite incorrectly assumed that latin1 strings were always ASCII strings.
Copy-on-write file uploads on Linux
When you upload a large file with FormData, each file becomes a Blob object (or File object) in memory.
What happens if you need to get the data as an ArrayBuffer?
const blob = formData.get("image");
// This clones the data, doubling the memory usage!
const bytes = await blob.arrayBuffer();
It clones. Suddenly, if you uploaded a 128 MB file, you're now using 256 MB of memory just to get the data as an ArrayBuffer.
One could argue that this is an API design issue. Should .arrayBuffer() really clone the data? But that's the standard Web API, it's what we have to work with.
Can we do better, without changing the API? Yes!
It's called copy-on-write memory.
Through some virtual-memory trickery, we can make it so that when you call blob.arrayBuffer(), it doesn't actually clone the data. Instead, it creates a new virtual memory mapping that points to the same data, and only clones the data (in chunks of 4 KB) when you write to it.
This release implements support for copy-on-write blob.arrayBuffer() when blob is at least 8 MB (1 MB when --smol is enabled).
| Memory usage after 100 copies | Bun version |
|---|---|
| 192 MB | Bun v1.0.21 |
| 12.9 GB | Bun v1.0.20 |
const blob = new Blob([new Uint8Array(1024 * 1024 * 128).fill(42)]);
const arrayBuffers = await Array.fromAsync({ length: 100 }, () =>
blob.arrayBuffer(),
);
console.log("RSS", (process.memoryUsage.rss() / 1024 / 1024) | 0, "MB");
If you make 100 copies of a 128 MB blob, it will only use 192 MB of memory. Before, it would use 12,993 MB MB of memory.
There is overhead to this, of course. Creating a unique memory mapping for each blob is expensive. Bun only does this for blobs larger than 8 MB (or 1 MB when --smol is enabled since --smol optimizes memory usage over execution time).
Hash file uploads from FormData with Bun.CryptoHasher
Bun.CryptoHasher now supports Blob objects, which simplifies hashing file uploads from FormData.
import { serve, CryptoHasher } from "bun";
serve({
async fetch(req) {
const formData = await req.formData();
const image = formData.get("file");
const sha1 = CryptoHasher.hash("sha1", image, "hex");
return new Response(`SHA-1: ${sha1}`);
},
});
Previously, you'd have to convert the Blob to an ArrayBuffer before hashing it.
import { serve, CryptoHasher } from "bun";
serve({
async fetch(req) {
const formData = await req.formData();
const image = formData.get("file");
const image = await formData.get("file").arrayBuffer();
const sha1 = CryptoHasher.hash("sha1", image, "hex");
return new Response(`SHA-1: ${sha1}`);
},
});Note that we haven't added support for Bun.file() to Bun.CryptoHasher yet. It's tricky because Bun.file() is asynchronous and Bun.CryptoHasher is synchronous. We'll investigate adding support for that in a future release.
expect(...).toBeObject is a new matcher in bun:test
The toBeObject matcher is now available in bun:test.
import { test, expect } from "bun:test";
test("object is an object", () => {
expect({}).toBeObject();
});
test("number is not an object", () => {
expect(1).not.toBeObject();
});
Thanks to @coratgerl for adding this matcher!
Fixed: Memory leak in Bun.file()
Previously, the following code would leak memory:
import { file } from "bun";
for (let i = 0; i < 100_000; i++) {
file("/tmp/" + i + "/foo.txt");
}
The file path string was being duplicated without being freed. This is now fixed.
Fixed: Memory leak in Bun.write()
Similar to the above, Bun.write would leak memory when called repeatedly.
import { write } from "bun";
for (let i = 0; i < 100_000; i++) {
await write("/tmp/" + i + "/foo.txt", "Hello!");
}
Again, the file path string was being duplicated without being freed. This is now fixed.
Fixed: Memory leak in errors from node:fs
When you call fs.readdir(path, { throwIfNoEntries: true }) and the directory doesn't exist, that error message would leak memory.
This has been fixed.
This leak wasn't specific to readdirSync, this occurred for nearly all errors from node:fs which contained a file path string or a message that was dynamically generated. This includes fs.readFile, fs.writeFile, fs.stat, fs.readlink, and more.
in the next version of Bun
— Jarred Sumner (@jarredsumner) December 28, 2023
a memory leak in errors from node:fs & Bun.write has been fixed pic.twitter.com/FuY34GLlB9
Fixed: bun build now supports --public-path
bun build now supports the --public-path flag, which allows you to specify the public path for your assets to be served from.
bun build ./entry.ts --public-path=/assets/ --outdir=distThis is useful if you're serving your assets from a CDN and want to make sure assets use absolute URLs.
This feature was already implemented in Bun.build, using the JavaScript API but was mistakenly omitted from the CLI.
Fixed: Crash in fetch() after redirect in certain cases
A crash that sometimes occurred when making multiple fetch() calls in quick succession with some that redirect has been fixed, thanks to @cirospaciari.
Fixed: Crash in WebSocket client when the server responds with a non-101 status code
A bug in the WebSocket client that potentially caused a crash when the server responds with unexpected HTTP response headers has been fixed. This bug happened more frequently on macOS than Linux.
Fixed: Crash in WebSocket client when TLS handshake fails
A bug in the WebSocket client that potentially caused a crash when the server responded with an invalid TLS certificate. This bug happened more frequently on macOS than Linux.
Fixed: Crash in Bun.listen & Bun.connect when TLS handshake fails
Similar to the above, a bug in Bun.listen and Bun.connect that potentially caused a crash when the server responded with an invalid TLS certificate has been fixed.
Fixed: Possible crash in bun test with invalid surrogate pairs in error messages
When an error message contained invalid surrogate pairs (or otherwise, invalid UTF-8) in bun test, it could cause a crash due to an assertion failure when converting the error message to UTF-8 before it reaches the terminal. This has been fixed.
Fixed: Extraneous terminating chunk in Bun.serve
A bug in Bun.serve that could cause an extra terminating empty chunk to be sent when streaming has been fixed, thanks to @cirospaciari.
Fixed: "call" property missing in console.log() output
A bug in console.log() that caused the "call" property to be missing in the output has been fixed.
console.log({ call: "where did i go??", a: 1 });
Before:
{
a: 1,
}
After:
{
call: "where did i go??",
a: 1,
}
Fixed: expect(error).toStrictEqual() compares only message & name
The expect(error).toStrictEqual() matcher now compares the message and name properties of the error, instead of all properties.
import { test, expect } from "bun:test";
test("errors match up", () => {
const err = new Error("foo");
err.stack;
expect(err).toStrictEqual(new Error("foo"));
});
This test now passes, which aligns the behavior with Jest and node's assert module. Thanks to @Kkaioduarte for fixing this bug!
Fixed: "duplicate dependencies" in bun install is now a warning instead of an error
When you run bun install and there are duplicate dependencies defined in package.json, it now warns instead of errors.
bun installbun install v1.0.21 (f721bbe3)
6 | "@types/bun": "latest",
^
warn: Duplicate dependency: "@types/bun" specified in package.json
at errory/package.json:6:5
10 | "@types/bun": "latest"
^
note: "@types/bun" originally specified here
at errory/package.json:10:21Thanks to @knightspore for fixing this bug!