Bun

Bun v1.1.41


Jarred Sumner · December 20, 2024

We're hiring systems engineers in San Francisco to build the future of JavaScript!

This release fixes 12 bugs (addressing 196 👍). JSDOM & happy-dom work more reliably due to node:vm compatibility improvements. bun build --compile gets the --windows-icon and --windows-hide-console options (Windows-only). bun install gets the --omit=dev|optional|peer options and in .npmrc. A hoisting edgecase in bun install is fixed

To install Bun

curl
npm
powershell
scoop
brew
docker
curl
curl -fsSL https://bun.sh/install | bash
npm
npm install -g bun
powershell
powershell -c "irm bun.sh/install.ps1|iex"
scoop
scoop install 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

JSDOM, happy-dom, and vm compatibility improvements

We've made compatibility improvements to our node:vm implementation, and that makes jsdom and happy-dom work more reliably in Bun.

JSDOM fixes

Previously, the runScripts: "outside-only", runScripts: "dangerously", and runInContext options in JSDOM were not supported. Now they are.

happy-dom fixes

happy-dom no longer throws an error when you call window.requestAnimationFrame, or when you call various other methods that depend on the this value being set correctly.

New: Icons in single-file executables for Windows

Single-file executables now support the --windows-icon=./icon.ico option to embed an icon in the executable on Windows.

bun build --compile --windows-icon=./icon.ico ./index.ts --outfile=app.exe

Thanks to @paperdave for implementing this!

New: Hide the console window in single-file executables for Windows

You can now hide the console window on Windows when a single-file executable is run.

bun build --compile --windows-hide-console ./index.ts --outfile=app.exe

New: throw: true in Bun.build

When the build fails, the throw: true option in Bun.build throws an error instead of returning errors in the logs array.

const build = Bun.build({
  entrypoints: ["./index.ts"],
  outdir: "./dist",
  throw: true,
});

We will make this behavior the default in Bun v1.2.

New: --omit=dev|optional|peer options in bun install

We want bun install to "just work" in existing projects using npm. We already support package-lock.json migration (and have for awhile), and this is another step towards that goal.

bun install now supports:

  • --omit=dev to omit dev dependencies (similar to --production)
  • --omit=optional to omit optional dependencies
  • --omit=peer to omit peer dependencies
  • ...and any combination of the above

We've also added support for include and exclude in .npmrc, and in bunfig.toml.

Thanks to @dylan-conway for implementing this!

New: expect().toHaveBeenCalledOnce()

You can now use expect().toHaveBeenCalledOnce() to check that a mock function was called exactly once.

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

test("fn is called once", () => {
  const fn = mock(() => {});
  expect(fn).not.toHaveBeenCalledOnce();
  fn();
  expect(fn).toHaveBeenCalledOnce();
  fn();
  expect(fn).not.toHaveBeenCalledOnce();
});

'use strict' is now CommonJS

Some CLIs REALLY want to trick bundlers and get the current module's file path, do a runtime require, or check if the current module is the main module. They try all kinds of things to make it work, such as:

"use strict";

if (eval("require.main") === eval("module.main")) {
  // ...
}

One of the hard parts about how Bun makes CommonJS and ESM interop "just work" is that there's a lot of ambiguity.

Consider this code:

console.log("123");

Is that CommonJS or ESM? No way to tell.

Okay, how about this?

console.log(module.require("path"));

CommonJS, because it's using module.require to get the path module.

And this?

import path from "path";
console.log(path);

ESM because it's using import.

And this?

import path from "path";
const fs = require("fs");
console.log(fs.readFileSync(path.resolve("package.json"), "utf8"));

ESM, because it's using import, and saying it's CommonJS due to the require would break the code. We want to simplify building stuff in JavaScript, so let's just say it's ESM and not be fussy.

And finally, how about this?

"use strict";

console.log(eval("module.require('path')"));

Previously, Bun would say "ESM" because it's what we default to when there's absolutely no way to tell (including when the file extension is ambiguous, no "type" field in package.json, no export, no import, no TLA, etc etc).

Now, Bun will say "CommonJS" because it had "use strict" at the top of the file.

ES modules are always strict mode, so an explicit "use strict" would only be useful to include if the file was originally intended to be CommonJS. Most build tools nowadays that output CommonJS will include the "use strict" directive at the top of the file. So we can use this as a last-chance heuristic when it's completely ambiguous whether the file is CommonJS or ESM.

Previously, an eval("require.main") would throw a ReferenceError because the spec-compliant behavior of eval inside of ESM is indirect (global scope), and even if it wasn't global scope, Bun replaces require with import.meta.require in ESM at transpilation time so that code would never work regardless. So, in the spirit of "your code should just work", we have added "use strict" at the top of the file to our repertoire of what we consider CommonJS.

Fixed: bun install progress bar shows total install count

Fixed: potential infinite loop with cached disabled optional dependencies in bun install

An infinite loop that could potentially occur caused by disabling optional dependencies and installing with a populated cache has been fixed.

Thanks to @dylan-conway for fixing this!

Fixed: unresolvable dependencies edgecase in bun install

When using platform-specific dependencies or disabling dependencies through --production, in certain rare cases bun install would hang due to incorrectly handling unresolvable dependencies.

This was caused by a spot where we were not sorting dependencies by their "behavior" (is it optional? peer? dev? etc). This led to inconsistent

Thanks to @dylan-conway for fixing this!

Fixed: --conditions=default in bun build

A crash that could occur when using --conditions=default in Bun.build has been fixed. Note: the default condition is used by default in Bun.build.

Thanks to @paperdave for fixing this!

Fixed: util.styleText supports color arrays

You can now pass an array of colors to util.styleText to style a string with multiple colors.

const styledText = util.styleText(
  ["red", "bold", "underline"],
  "Hello, world!",
);

This aligns the behavior with Node.js

Fixed: unnecessary inline sourcemaps in VSCode

When you have Bun's official VSCode extension installed, Bun automatically reports runtime errors in the "problems" tab of VSCode to give you real-time in-editor error reporting.

This involves a WebKit inspector connection that occurs when you ordinarily launch Bun from your VSCode terminal.

When the debugger is connected, Bun internally appends inline sourcemaps to transpiled JavaScript files so the debugger can find the original source code and map errors, messages, breakpoints files, etc. This is important for debugging, but it costs you memory and startup time to append these base64-encoded sourcemaps to every single file that loads.

These sourcemaps are only necessary for the debugger, and not for the "problems" tab feature in VSCode (which sends over pre-sourcemapped errors instead of sourcemapping in the extension). This release disables inline sourcemaps when only the "problems" tab is being used.

Thanks to @riskymh for fixing this!

Fixed: Theoretical crash in CommonJS module loading

A hypothetical crash that could occur in CommonJS module loading has been fixed.

Bun transpiles every file before it loads.

CommonJS modules have a function wrapper that looks something like this:

(function (exports, require, module, __filename, __dirname) {
  // module code
});

In Bun's CommonJS module loader, we were not handling the scenario where a module does not evaluate to a function. The transpiler always outputs a function. That being said, it's theoretically possible that a module could evaluate to something other than a function, and so we fixed it anyway.

As part of this, we also moved the step where it assigns the module.require and module.require.resolve function to occur after the outer function wrapper has been evaluated instead of before. There should in theory not be an observable behavior difference, but if you find anything please let us know!

Fixed: import namespaces and default imports for macros

You can now import namespaces and default imports for macros.

import myMacro from "at-build-time" with {type: "macro"};
import * as allMyMacros from "at-build-time" with {type: "macro"};

myMacro();
allMyMacros.myMacro();

Thanks to @brainkim for fixing this!

Thanks to 6 contributors!