Bun v0.5.8 introduces expect()
snapshots in bun test
, preloading modules using bun --preload
, improvements to Web API & Node.js compatibility, lots of bug fixes, and a teaser of what's next.
curl -fsSL https://bun.sh/install | bash
npm install -g 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
We're hiring C/C++ and Zig engineers to build the future of JavaScript!
Snapshot testing with bun test
bun test
aims to be a high-performance, drop-in replacement for Jest. Now it has support for snapshot testing, using expect(value).toMatchSnapshot()
, which supports the same snapshot format as Jest. Here's how it works:
First, create a test.
import { test, expect } from "bun:test"; // "bun:test" imports are optional
test("can test a snapshot", async () => {
const response = await fetch("https://example.com/");
const body = await response.text();
expect(body).toMatchSnapshot();
});
Then, run the test file using bun test
.
bun test snapshot.test.ts
Next, you'll see the following output in your terminal.
snapshot.test.ts:
✓ can test a snapshot
1 pass
0 fail
snapshots: +1 added
1 expect() calls
Ran 1 tests across 1 files [48.00ms]
Finally, you'll see there is a newly-generated snapshot file. Bun uses the same snapshot format as Jest, which means you don't need to re-generate your snapshots when migrating from Jest to bun test
.
exports[`can test a snapshot 1`] = `
"<!doctype html>
<html>
<head>
<title>Example Domain</title>
</head>
</html>
"
`;
If you need to update your snapshots, you can pass the --update-snapshots
flag, which will update snaphots for every test file that is run.
bun test --update-snapshots
In addition to toMatchSnapshot()
, Bun now supports these new matchers:
toThrow(RegExp)
(in addition totoThrow(string|Error)
)toBeInstanceOf()
(thanks to @zhiyuang)toMatch()
(thanks to @zhiyuang)
We also added a flag to re-run a test file multiple times, to help with reproducing errors in flaky tests.
bun test --rerun-each=5
This reruns each test file 5 times.
bun --preload <preload> [command]
Bun can now preload files before running a file or a test suite. This behavior is analogous to node --require
.
To preload a file via the --preload
flag:
bun --preload ./preload.ts run ./index.ts
To configure a list of files to preload
in bunfig.toml
:
preload = ["./preload.ts"]
This is useful for registering plugins
, injecting globals, or performing setup tasks without cluttering your scripts or entrypoints with extra import()
or require()
statements.
Here's an example of how to preload a Bun.plugin()
to load .yml
files.
import config from "./config.yml";
console.log(config);
enabled: true
import { plugin } from "bun";
plugin({
name: "YAML",
async setup(build) {
const { load } = await import("js-yaml");
const { readFileSync } = await import("fs");
// when a .yaml file is imported...
build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => {
// read and parse the file
const text = readFileSync(args.path, "utf8");
const exports = load(text) as Record<string, any>;
// and returns it as a module
return {
exports: {default: exports, ...exports},
loader: "object", // special loader for JS objects
};
});
},
});
You can now import .yml
files from your entrypoint:
bun --preload ./yaml.plugin.ts index.ts
{
enabled: true
}
To avoid the --preload
flag, configure bunfig.toml
:
preload = ["./yaml.plugin.ts"]
Running bun test
or bun run
will now preload the plugin automatically.
bun run index.ts
{
enabled: true
}
Partial glob support in bun install
workspaces
The following pattern is now supported in "workspaces"
with bun install:
{
"name": "myWorkspace",
"workspaces": [
// Make all folders with a package.json and a name in packages/ part of the workspace
"packages/*"
]
}
You can use a /*
to add all folders matching the prefix into your workspace. This is useful for monorepos that have many packages.
For now, only a single glob pattern at the very end (foo/*
) is supported. We plan to improve our glob support in the future. In the meantime, we also added better error messages when a glob pattern isn't implemented yet.
Web Compatibility
Thanks to improvements to Bun's test coverage, we've fixed a number of bugs in Bun's Web API compatibility.
request.bodyUsed
& response.bodyUsed
request.bodyUsed
and response.bodyUsed
were inconsistent with the spec when using null
or undefined
bodies.
const response = new Response(null);
await response.arrayBuffer();
console.log(response.bodyUsed);
/*
Incorrect:
Bun v0.5.7: true
Correct:
Bun v0.5.8: false
Chrome: false
Safari: false
*/
Consuming Request
& Response
bodies
null
or undefined
Request
& Response
bodies can now be consumed multiple times, which aligns the behavior with Chrome & Safari.
Previously, Bun would throw an error when trying to consume a null
or undefined
body twice:
bun-0.5.7 response.js
1 | const response = new Response(null);
2 | await response.arrayBuffer();
3 | console.log(await response.arrayBuffer());
^
error: Body already used
Now .arrayBuffer()
on null
or undefined
body returns an empty ArrayBuffer
instead:
bun response.js
ArrayBuffer(0) [ ]
/*
Incorrect:
Bun v0.5.7: <error> "Body already used"
Correct:
Bun v0.5.8: ArrayBuffer(0) [ ]
Chrome: ArrayBuffer(0) [ ]
Safari: ArrayBuffer(0) [ ]
*/
Similarly, Response.prototype.body
is supposed to return null
when there is no body, but Bun was returning an empty ReadableStream
instead.
const response = new Response(null);
console.log(response.body);
/*
Incorrect:
Bun v0.5.7: ReadableStream { ... }
Correct:
Bun v0.5.8: null
Chrome: null
Safari: null
*/
blob.slice()
contentType
argument
blob.slice
accepts an optional third argument for the type
of the new Blob
. Previously, Bun didn't support this argument.
const blob = new Blob(["Hello, world!"], { type: "text/plain" });
const slice = blob.slice(0, 5, "text/html; charset=utf-8");
console.log(slice.type); // "text/html; charset=utf-8"
/*
Incorrect:
Bun v0.5.7: ""
Correct:
Bun v0.5.8: "text/html; charset=utf-8"
Chrome: "text/html; charset=utf-8"
Safari: "text/html; charset=utf-8"
*/
Request
constructor
The Request
constructor in Bun had a couple bugs.
When given two arguments, it always expected the first argument to be a string, which is incorrect.
// This didn't work: (inconsistent with the spec)
new Request(new Request("https://example.com"), {});
// But any of these did:
new Request(new Request("https://example.com"));
new Request("https://example.com", { method: "POST" });
new Request("https://example.com", {});
That has been fixed, and now the first argument can be a string or a Request
object.
// This works now:
console.log(new Request(new Request("https://example.com"), {}).url);
// "https://example.com"
Another bug: the url
argument was not being validated correctly (validated in fetch()
but not in new Request()
)
// this is supposed to throw:
const request = new Request("", { method: "POST" });
Now it (correctly) throws an error:
bun /tmp/app.js
1 | const request = new Request("", { method: "POST" });
^
error: Failed to construct 'Request': url is required.
Bun's test coverage for the Request
constructor was lacking and Bun's implementation of fetch
(which does validate) doesn't use the same code path as the Request
constructor.
Node.js compatibility
Bun continues to make improvements to Node.js compatibility. Here's what has changed since the last release.
- Dramatically improved
Buffer
implementation and is now passing many more Node.js tests - Implemented
os.cpus()
for macOS (previously, was only supported on Linux) - Implemented
os.networkInterfaces()
- Added
AbortSignal
support tohttp.Server
andhttp.ClientRequest
node:net
's server functionlisten
callback now fires after the server is listening, instead of sometimes firing before the server is listeningnode:zlib
was missing the export forconstants
Bun.sleepSync()
To fix an inconsistency issue between Bun.sleep()
and Bun.sleepSync()
, there is a breaking change where Bun.sleepSync()
now accepts milliseconds, instead of seconds. Using code search on GitHub, we found that most usages were already assuming the units were milliseconds.
Bun.sleepSync(1); // sleep for 1 ms (not recommended)
await Bun.sleep(1); // sleep for 1 ms (recommended)
What's next?
For Bun v0.6, you can expect big changes, including a brand new bundler!
smaller bundle size than webpack (after minification) pic.twitter.com/OptyA2TSCY
— Jarred Sumner (@jarredsumner) March 17, 2023
Bun's upcoming bundler intends to make building modern, full-stack JavaScript and TypeScript apps a lot simpler (and faster).
Of course, we continue to make progress towards our priorities of Node.js compatibility, overall stability, and performance as Bun inches closer to a 1.0 release. To measure progress towards these goals we've dramatically increased our test coverage of Bun, Node.js, and standard-Web APIs. Stay tunned.
Changelog
#2156 | Fixed {Request,Response}.body not being null by @cirospaciari |
#2115 | Implemented os.cpus() for macOS by @jwhear |
#2161 | Fixed HTMLRewriter.onDocument() not being run by @jwhear |
#2144 | Fixed incorrect return value for dns.{resolve4,resolve6}() by @cirospaciari |
599f63c | Supported older releases of macOS, including 10.15 by @Jarred-Sumner |
0a7309c | Improved performance of EventEmitter by @Jarred-Sumner |
eb94e5b | Changed BoringSSL to use heap from mimalloc by @Jarred-Sumner |
#2192 | Improved stability of bun pm ls by @alexlamsl |
693be3d | Improved performance of ASCII case-insensitive comparisons by @Jarred-Sumner |
#2202 | Fixed ANSI escape codes not being piped from stdout to a file by @alexlamsl |
5d296f6 | Fixed an issue where shasum was being parsed twice by @Jarred-Sumner |
#2213 | Fixed an issue with bun install and duplicate dependencies by @alexlamsl |
#2142 | Implemented os.networkInterfaces() by @jwhear |
#2191 | Fixed incorrect port for bun dev by @xjmdoo |
#2143 | Fixed issues with fetch({ signal }) on shutdown by @cirospaciari |
#2223 | Implemented http.listen({ signal }) by @cirospaciari |
#2222 | Implemented http.get({ signal }) by @cirospaciari |
#2226 | Removed transient error when sqlite was given an empty statement by @ThatOneBro |
#2231 | Implemented bun --preload <file> by @Jarred-Sumner |
#2249 | Fixed issue with Bun.file().arrayBuffer() on an empty file by @cirospaciari |
#2258 | Fixed bug where http.request() did not accept URL s by @ThatOneBro |
#2262 | Improved compatibility of http.request() when method is GET or HEAD and has a body by @ThatOneBro |
#2242 | [BREAKING] Bun.sleepSync() now takes milliseconds, instead of seconds by @jwhear |
#2276 | Fixed os.tmpdir() from removing trailing slashing by @cirospaciari |
#2285 | Fixed request.url not including search params in node:http by @zhiyuang |
#2295 | Changed bunx without parameters to show a help page by @Zeko369 |
#2288 | Fixed headers not being lowercase in node:http by @ThatOneBro |
#2293 | Fixed bunx from not being able to find scoped npm packages by @Zeko369 |
#2302 | Fixed an issue where "latest" would not be considered in semver range by @alexlamsl |
#2307 | Fixed bun install not reporting connection errors to the npm registry by @alexlamsl |
#2314 | Implemented expect().toThrow(RegExp) by @alexlamsl |
#2315 | Fixed error messages for Blob on Linux by @alexlamsl |
#2320 | Implemented some of tty.WriteStream for process.{stdout,stderr} by @ThatOneBro |
0a9cb0e | Fixed issues when HTMLRewriter would receive an empty string by @Jarred-Sumner |
#2331 | Fixed parameter errors with crypto.scryptSync() by @dylan-conway |
#2341 | Improved Buffer compatibility with Node.js by @alexlamsl |
e16053c | Fixed an issue where crypto.createHash().update("binary") would not work by @Jarred-Sumner |
#2340 | Improved spec-compliance of Blob.type by @Jarred-Sumner |
#2371 | Fixed error when require.resolve() argument is empty by @paperdave |
#2337 | Implemented unix socket support in net.Server by @cirospaciari |
4c38798 | Fixed incorrect encoding of Socket.remoteAddress by @Jarred-Sumner |
27f5012 | Fixed node:https from being readonly by @Jarred-Sumner |
#2389 | Implemented expect().toBeInstanceOf() by @zhiyuang |
e613b50 | Implemented missing constants in node:zlib by @Jarred-Sumner |
#2294 | Implemented expect().toMatchSnapshot() by @dylan-conway |
#2397 | Fixed an issue where parsing a malformed bun.lockb by @alexlamsl |
#2400 | Fixed an issue where Bun.serve() could not listen on IPv6 by @cirospaciari |
9a5f78f | Fixed console.log() not showing [Getter] values by @Jarred-Sumner |
#2404 | Implemented expect().toMatch(RegExp) by @zhiyuang |
Also, if you haven't heard, Bun has docs now!
A huge thanks to @colinhacks, as well as the many folks that have contributed since the last release: @jakeboone02, @charliermarsh, @BrettBlox, @damianstasik, @Sheraff, @johnnyreilly, @fdaciuk, @akash-joshi, @TommasoAmici, @raxityo, @DreierF, @rmorey, @cunzaizhuyi, @rodoabad, @gaurishhs, @maor-benami, @aabccd021, @pfgithub, @bushuai, @Zeko369, @noahmarro, @harisvsulaiman, @nskins, @milesj, and @jsoref.