We're hiring C/C++ and Zig engineers to build the future of JavaScript! Join our team →
We've been releasing a lot of changes to Bun recently, here's a recap in case you missed it:
v0.6.0
- Introducingbun build
, Bun's new JavaScript bundler.v0.6.2
- Performance boosts: 20% fasterJSON.parse
, up to 2x fasterProxy
andarguments
.v0.6.3
- Implementednode:vm
, lots of fixes tonode:http
andnode:tls
.v0.6.4
- Implementedrequire.cache
,process.env.TZ
, and 80% fasterbun test
.v0.6.5
- Native support for CommonJS modules (previously, Bun did CJS to ESM transpilation),v0.6.6
-bun test
improvements, including Github Actions support,test.only()
,test.if()
,describe.skip()
, and 15+ moreexpect()
matchers; also streaming file uploads usingfetch()
.v0.6.7
- Node.js compatibility improvements to unblock Discord.js, Prisma, and Puppeteer
This release implements a top-level Bun.password
API for secure password hashing, function mocking & toMatchObject
in bun test
, and an experimental inspector
mode in Bun.serve()
, in addition to a range of bugfixes and stability improvements. We have also made a breaking change to bun:sqlite
.
To install Bun:
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
Bun.password
Bun now exposes the Bun.password
API for conveniently hashing passwords in a cryptographically secure way.
const password = "super-secure-pa$$word";
const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4
The hashing algorithm and its parameters are encoded into the returned hash, so hashes can be verified with Bun.password.verify
with no additional metadata provided.
const password = "super-secure-pa$$word";
const hash = await Bun.password.hash(password);
const isMatch = await Bun.password.verify(password, hash);
// => true
The default algoritm is argon2
but bcrypt
is also supported. Both algorithms can be configured as needed.
const password = "super-secure-pa$$word";
// use argon2 (default)
const argonHash = await Bun.password.hash(password, {
algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
memoryCost: 4, // memory usage in kibibytes
timeCost: 3, // the number of iterations
});
// use bcrypt
const bcryptHash = await Bun.password.hash(password, {
algorithm: "bcrypt",
cost: 4, // number between 4-31
});
When using bcrypt
, the returned hash is encoded in Modular Crypt Format for compatibility with most existing bcrypt
implementations; with argon2
the result is encoded in the newer PHC format. Under the hood Bun.password
uses the std.crypto.pwhash
module from Zig's standard library.
Refer to API > Hashing > Bun.password
for complete documentation.
Function mocking in bun test
Bun's test runner now supports function mocking and a couple related expect()
matchers.
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());
test("random", async () => {
const val = random();
expect(val).toBeGreaterThan(0);
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(1);
});
The result of mock()
is a new function that's decorated with some additional properties and methods.
import { mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());
random(2);
random(10);
random.mock.calls;
// [[ 2 ], [ 3 ]]
random.mock.results;
// [
// { type: "return", value: 0.6533907460954099 },
// { type: "return", value: 0.6452713933037312 }
// ]
Creating mocks in bun:test is low-overhead and fast. In a benchmark creating 100,000 mocks, bun:test was 80x faster than Jest and 100x faster than Vitest.
Creating 100,000 mocks
— Jarred Sumner (@jarredsumner) June 9, 2023
bun:test: 5ms
jest: 418ms (80x slower)
vitest: 786ms (100x slower) pic.twitter.com/a3tjwrdT9u
jest.spyOn
support
Support has been added for Jest's spyOn
functionality as well. This provides a mechanism for tracking and introspecting method calls.
import { test, expect, spyOn } from "bun:test";
const ringo = {
name: "Ringo",
sayHi() {
console.log(`Hello I'm ${this.name}`);
},
};
const hiMock = spyOn(ringo, "sayHi");
test("spyon", () => {
expect(hiMock).toHaveBeenCalledTimes(0);
ringo.sayHi();
expect(hiMock).toHaveBeenCalledTimes(1);
});
For ease of migration, a polyfill for the jest
global is exported from bun:test
. This polyfill implements jest.fn
and jest.spyOn
for codebases that already rely on Jest-style syntax.
import { test, expect, jest } from "bun:test";
const mockFn = jest.fn(() => {});
const mockMethod = jest.spyOn(
{
random() {
return Math.random();
},
},
"random",
);
Support has been added for the .toHaveBeenCalled()
and .toHaveBeenCalledTimes()
matchers. Contributors are welcome to submit PRs for additional matchers like .toHaveBeenCalledWith()
and .toHaveBeenLastCalledWith()
.
We haven't added support for module mocks yet. Stay tuned for that in a future release, though I'm pretty sure we can make it work for both CommonJS and ES Modules using the same API.
toMatchObject
The toMatchObject
matcher has been added to bun:test
. This matcher is useful for asserting that an object contains a subset of properties.
import { test, expect } from "bun:test";
test("toMatchObject", () => {
const obj = {
a: 1,
b: 2,
c: 3,
};
expect(obj).toMatchObject({
a: 1,
b: 2,
});
expect(obj).not.toMatchObject({
c: 4,
});
});
You can also use toMatchObject
with asymmetric matchers like expect.stringContaining
and expect.any(Number)
:
import { test, expect } from "bun:test";
test("toMatchObject", () => {
const obj = {
a: "hello",
b: "world",
};
expect(obj).toMatchObject({
a: expect.stringContaining("ell"),
b: expect.any(String),
});
});
(breaking) bun:sqlite's .values()
method now returns []
instead of null
Previously, the following returned null
due to no matching rows:
db.query("SELECT * FROM foo WHERE id > 9999").values();
// null
As of Bun v0.6.8, it returns []
:
db.query("SELECT * FROM foo WHERE id > 9999").values();
// []
This aligns with the behavior of other functions and other SQLite3 libraries.
db.query("SELECT * FROM foo WHERE id > 9999").all(); // []
db.query("SELECT * FROM foo WHERE id > 9999").values(); // [] (previously: null)
We conducted a Twitter poll and 75% voted in favor of the breaking change.
calling .values() on a bun:sqlite query with 0 results returns null instead of []
— Jarred Sumner (@jarredsumner) June 6, 2023
keep the confusing API the same or make the breaking change?
Experimental inspector
mode in Bun.serve
Bun's HTTP server now provides an "inspector" mode. To enable it, set inspector: true
. Setting NODE_ENV=production
disables inspector mode, regardless of the value passed into Bun.serve
.
Bun.serve({
inspector: true,
fetch(req) {
// handle request
},
});
This mounts a WebSocket-based debugger at localhost:$PORT/bun:inspect
that can process messages conforming to WebKit's debugger protocol. This is a first step towards a more advanced browser-side debugging experience when developing full-stack applications with Bun.
This gist provides a simple but functional implementation of an in-browser "REPL" that can execute code server-side and retrieve the results over WebSockets.
To make it easier to develop tooling that targets the WebKit debugger protocol, we've published bun-devtools
to npm
, which provides auto-generated Typecript types for the protocol's actions, logs, and more.
import { JSC } from "bun-devtools";
Transpiler & Bundler bugfixes
We fixed a few things in the bundler & transpiler
lodash-es/isBuffer failed to bundle
The lodash-es
export isBuffer
was failing to run due to an edgecase with CommonJS and ES Module interop.
The following from lodash-es/isBuffer.js
:
import root from "./_root.js";
import stubFalse from "./stubFalse.js";
var freeExports =
typeof exports == "object" && exports && !exports.nodeType && exports;
var freeModule =
freeExports &&
typeof module == "object" &&
module &&
!module.nodeType &&
module;
var moduleExports = freeModule && freeModule.exports === freeExports;
var Buffer = moduleExports ? root.Buffer : undefined;
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;
var isBuffer = nativeIsBuffer || stubFalse;
export default isBuffer;
The typeof
check for module
and exports
is incorrect because the module is always an ES Module, so module
and exports
are always undefined. The usage of CommonJS identifiers caused Bun to treat it as a CommonJS module, which caused a runtime exception. That has been fixed.
However, the isBuffer
function in lodash-es
remains broken in both Node.js & Bun (it always returns false
). We have filed an issue with lodash-es
.
\r\n normalization in tagged template literals
Bun was incorrectly normalizing Windows newlines in tagged template literals in certain cases. This has been fixed.
v
flag in RegExp
Bun was incorrectly throwing a syntax error when the v
flag was used in a RegExp literal. The v
flag is a stage4 TC39 proposal.
bun install bugfix for workspace lifecycle scripts
bun install now runs lifecycle scripts for workspace packages. Previously, only the root package's lifecycle scripts were run.
Thanks to @alexlamsl for the fix!
console.log(myBuffer) shows Buffer instead of Uint8Array
Previously, console.log
would show Uint8Array
instead of Buffer
for Buffer
instances. This has been fixed.
const buf = Buffer.from("hello world");
console.log(buf);
Previously:
Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
Now:
Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
Changelog
#3209 | [Transpiler] Fix normalizing \r\n in tagged template string literals by @Jarred-Sumner |
#3208 | removeAllListeners return this by @dylan-conway |
#3204 | Implement Bun.password and Bun.passwordSync by @Jarred-Sumner |
#3215 | [transpiler] Fix new length for raw template contents by @dylan-conway |
#3213 | allow v flag in regexp literal by @dylan-conway |
#3220 | console.log(Buffer.from('a')) -> Buffer(1) [... instead of Uint8Array(1) [... by @Jarred-Sumner |
#3219 | [breaking][bun:sqlite] .values() returns [] instead of null by @Jarred-Sumner |
#3254 | replace sudo usage in GitHub Actions by @alexlamsl |
#3231 | Fix to retain a newline after removing a package by @ytakhs |
#3252 | Implement mocks in bun:test by @Jarred-Sumner |