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 | 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 upgradeWe'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.tsNext, 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-snapshotsIn 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.tsTo 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.js1 | const response = new Response(null);
2 | await response.arrayBuffer();
3 | console.log(await response.arrayBuffer());
^
error: Body already usedNow .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.js1 | 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
Bufferimplementation and is now passing many more Node.js tests - Implemented
os.cpus()for macOS (previously, was only supported on Linux) - Implemented
os.networkInterfaces() - Added
AbortSignalsupport tohttp.Serverandhttp.ClientRequest node:net's server functionlistencallback now fires after the server is listening, instead of sometimes firing before the server is listeningnode:zlibwas 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 URLs 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 @paperclover |
#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.