Bun

Bun v0.6.10


Ashcon Partovi · June 26, 2023

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 - Introducing bun build, Bun's new JavaScript bundler.
  • v0.6.2 - Performance boosts: 20% faster JSON.parse, up to 2x faster Proxy and arguments.
  • v0.6.3 - Implemented node:vm, lots of fixes to node:http and node:tls.
  • v0.6.4 - Implemented require.cache, process.env.TZ, and 80% faster bun 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+ more expect() matchers; also streaming file uploads using fetch().
  • v0.6.7 - Node.js compatibility improvements to unblock Discord.js, Prisma, and Puppeteer
  • v0.6.8 - Introduced Bun.password, mocking in bun test, and toMatchObject()
  • v0.6.9 - Less memory usage and support for non-ascii filenames

To install Bun:

curl
npm
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
brew
brew tap oven-sh/bun
brew install bun
docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun

To upgrade Bun:

bun upgrade

Improved CommonJS support

Loading npm packages in Bun is more reliable now. This is because we rewrote CommonJS module loading (again). We've fixed many crashes related to loading CommonJS modules and improved our CommonJS module loader to be more compatible with Node.js.

webpack now works in Bun

require.main is now supported

require.main is a popular way for Node.js CLIs to check if the currently executing script started the process:

if (require.main === module.id) {
  // do something
}

Bun now supports require.main which unblocks several Node.js CLIs.

We've also added process.mainModule which is an alias of require.main.

console.log(require.main === process.mainModule); // true

__esModule annotation is now supported

Bun now has runtime support for the __esModule annotation used by bundlers & TypeScript to indicate what default export should point to when importing a CommonJS module as an ES Module.

__esModule is an annotation that preserves the export default <value> for ESM that was converted into CommonJS and subseqntly re-imported back into ESM.

Should import a from 'b' reference module.exports or module.exports.default?

This question is one of the things that has made the CommonJS -> ES Module transition difficult for the JavaScript ecosystem.

__esModule is a way to preserve the default import which is generated by Webpack, Babel, esbuild and used in many packages on npm. Bun now has runtime support for this.

That means the following input ES Module:

export default 42;

Would typically be converted by build tools into CommonJS published to npm as something like:

exports.default = 42;
exports.__esModule = true;

The __esModule annotation preserves the default value when imported back into ESM:

import foo from "./foo.js";
console.log(foo); // 42

Note that, in Node.js, the default export is not preserved:

import foo from "./foo.js";

// This is already the "default" export
// so why should you have to do .default to get the original value?
console.log(foo.default); // 42

For continued compatibility with Node.js, this behavior is disabled when there is no __esModule annotation OR when the enclosing package.json sets "type" to "module" (meaning, this is used in situations where Node.js would normally not support ESM anyway).

WebSocket client encoding fix

We fixed an encoding bug in Bun's client implementation of WebSocket, which could cause a text frame with latin1 encoding to be truncated.

Thanks to these changes, support for puppeteer has been improved in Bun.

import puppeteer from "puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com/");
await page.click("a");
await browser.close();

Improvements to bun:test

Bun now supports more matchers when using bun test:

import { test, expect } from "bun:test";

test("new matchers", () => {
  expect(fetch("http://example.com")).resolves.toBeInstanceOf(Response);
  expect(fetch("invalid")).rejects.toBeInstanceOf(Error);
  expect([]).toBeArray();
  expect([1, 2, 3]).toBeArrayOfSize(3);
  expect("").toBeTypeOf("string");
});

There is also improved support for mocking with spyOn().

import { test, expect, spyOn } from "bun:test";

test("spyOn()", () => {
  const service = {
    hasPermission: () => false,
  };

  let hasPermission = false;
  const spy = spyOn(service, "hasPermission").mockImplementation(
    () => hasPermission,
  );
  hasPermission = true;

  expect(service.hasPermission()).toBe(true);
  expect(spy).toHaveBeenCalled();
});

With all these changes, it should be faster and easier than ever to migrate your existing project to use bun test. Here's what happens a popular npm package, such as zod, migrates to bun test.

Bugfixes to .env support

Bun has builtin support for loading .env files into process.env, saving you from needing to run require("dotenv").config() on startup, allowing you to use .env files in your tests, and in package.json "scripts".

There were several bugs with this functionality that @alexlamsl has fixed:

  • Windows-style newlines could cause it to crash https://github.com/oven-sh/bun/issues/411
  • .env with $ inside the value of an environment variable were considered a nested variable when they shouldn't have been https://github.com/oven-sh/bun/issues/2823
  • A crash on inputs of certain lengths https://github.com/oven-sh/bun/issues/3042

Node.js compatibility improvements

Watch files in Bun

fs.watch() is now implemented!

import { watch } from "node:fs";

// file path
watch(".", (eventType, filename) => {
  console.log(`event type = ${eventType}`);
  if (filename) {
    console.log(`filename = ${filename}`);
  }
});
  • Bun.argv now matches process.argv.
import { deepEquals } from "bun";

console.log(deepEquals(Bun.argv, process.argv)); // true
function getStack() {
  const cause = new Error();
  const prevPrepareStackTrace = Error.prepareStackTrace;
  Error.prepareStackTrace = (_, stack) => {
    console.log(stack[0].getFunctionName()); // "getStack"
    console.log(typeof stack[0].getLineNumber()); // "number"
    console.log(typeof stack[0].getColumnNumber()); // "number"
  };
  Error.captureStackTrace(cause);
  Error.prepareStackTrace = prevPrepareStackTrace;
}
import { openSync, writevSync } from "node:fs";

const fd = openSync("path/to/file");
const buffers = [new Uint8Array(3), new Uint8Array(3), new Uint8Array(3)];
console.log(writevSync(fd, buffers)); // 9
  • Module._nodeModulePaths is now supported. This function returns an array of paths that Bun will search for modules in.
import { _nodeModulePaths } from "node:module";

console.log(_nodeModulePaths(".")); // ["/path/to/node_modules", "/path/node_modules", "/node_modules"]
  • crypto.randomInt() is now supported, thanks to @lenovouser
  • util.deprecate now supports "code" argument
  • fs.createWriteStream now supports passing flags correctly, thanks to @Hanaasagi

Improvements to bun install

We've also made a few additions to bun install.

--exact <package>

bun install --exact gives you the same behavior as npm install --save-exact or yarn add --exact. It pins the version of the package you're installing to the exact version you specify.

bun install --exact elysia@0.5.20

--frozen-lockfile

In this mode, Bun will throw an error if the bun.lock file is out of sync with package.json. This is useful for CI environments, where you want to ensure that bun.lock is up to date.

bun install --frozen-lockfile

trustedDependencies

You can now configure an allow-list of dependencies to run lifecycle scripts, such as postinstall. By default, these scripts are not run to make Bun more secure by default.

package.json
{
  "trustedDependencies": [
    "puppeteer"
  ]
}

We are planning to add a default list in the near future so that installing packages that rely on postinstall works as expected by default.

More bug fixes

  • A crash that could occur when writing files with Bun.write has been fixed
  • The node:tls module was missing some ES exports
  • The bun:sqlite package was missing a default export for the Database class
  • mkdirSync(path, {recursive: true}) would sometimes return an empty string when it shouldn't have
  • File names in exception logs were sometimes garbled and that has been fixed
  • Fixed a bug where module resolution for file: URLs with space characters in them would be % encoded instead of decoded
  • Fixed a crash when readdir returns a large directory and some elements are UTF-16
  • Fixed a potential crash when booelan arguments in node:fs functions are passed as non-boolean arguments

More new stuff

  • The lastInTextNode property in HTMLRewriter's TextChunk is now supported which tells you if the current chunk of text is the last one. Thanks to @bru02

Changelog

#3310Changed Bun.argv to be same as process.argv by @Jarred-Sumner
7f535a2Fixed issue when assigning module.require by @Jarred-Sumner
#3314Improved validation of Bun.serve options by @cirospaciari
#3320Fixed bugs with CommonJS imports by @Jarred-Sumner
#3337Fixed bug where const decleration could be tree-shaked before usage by @dylan-conway
8ad9e57Added missing alias for ucs2 encoding by @Jarred-Sumner
b951c1fImproved memory usage across the board by @Jarred-Sumner
#3362Improved detection of ESM and CommonJS when file has no exports by @Jarred-Sumner
#3316Implemented toBeArray, toBeArrayOfSize, and toBeTypeOf for bun:test by @TiranexDev
#3359Implemented more of V8's stack trace APIs by @kvakil
#3363Fixed overflow of fs.utimesSync() by @Jarred-Sumner
83d7ec7Fixed bug with .copy() after .digest() with node:crypto by @Jarred-Sumner
#3367Implemented asymetric matcher support for toEqual, toStrictEqual, and toHaveProperty by @dylan-conway
#3360Fixed bugs with JSX in classic mode by @dylan-conway
#3368Improved error message with circular dependencies by @Jarred-Sumner
#3369Fixed crash with .env files by @Jarred-Sumner
#3304Implemented more support for mock() in bun:test by @paperdave
#3378Fixed usage of bun:test in CommonJS files by @Jarred-Sumner
#3347Fixed various bugs with .env file parsing by @alexlamsl
#3318Implemented resolves and rejects for expect() by @Electroid
#3377Fixed bug with node-fetch import by @paperdave
#3376Implemented spyOn() in bun:test by @paperdave
#3249Implemented fs.watch() by @cirospaciari
#3379Rewrote support for CommonJS by @Jarred-Sumner
#3394Fixed bug with latin1 strings in WebSocket by @Jarred-Sumner
#3400Fixed potential crash with bun install --production by @Jarred-Sumner
5bd94b8Implemented process.mainModule by @Jarred-Sumner
#3402Fixed bug with constructor of WriteStream by @Hanaasagi
#3405Implemented support for embedding files in compiled executables by @Jarred-Sumner
#3365Implemented bun install --frozen-lockfile by @tiagotex
#3356Update lol-html by @bru02
#3401Fixed crash with lol-html by @Jarred-Sumner
#3411Implemented _nodeModulePaths and require.main.paths by @dylan-conway
#3288Implemented trustedDependencies in package.json by @alexlamsl
#3419Implemented writev and readv by @Jarred-Sumner
#3393Implemented __esModule annotations by @Jarred-Sumner

Full Changelog