Bun

Bun v1.0.12


Ashcon Partovi ยท November 16, 2023

Bun v1.0.12 fixes 24 bugs, makes bun's CLI help menus easier to read, adds support for bun -e, bun --env-file, server.url, import.meta.env, expect.unreachable(), improves support for module mocks, allows importing builtin modules at bundle-time, and improves Node.js compatibility.

Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager โ€” all in one. In case you missed it, here are some of the recent changes to Bun:

  • v1.0.8 - Fixes 138 bugs (addressing 257 ๐Ÿ‘ reactions), makes require() use 30% less memory, adds module mocking to bun test, fixes more bun install bugs
  • v1.0.9 - Fixes a glibc symbol version error, illegal instruction error, a Bun.spawn bug, an edgecase with peer dependency installs, and a JSX transpiler bugfix
  • v1.0.10 - Fixes 14 bugs (addressing 102 ๐Ÿ‘ reactions), node:http gets 14% faster, stability improvements to Bun for Linux ARM64, bun install bugfix, and node:http bugfixes
  • v1.0.11 - Fixes 5 bugs, adds Bun.semver, fixes a bug in bun install and fixes bugs impacting astro and @google-cloud/storage

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

bun -e evaluates a script

Bun now supports the -e flag, which is short for --eval, and allows you to run a script without creating a file. This is useful for quick scripts where you don't want to create a file and is similar to node -e.

bun -e "console.log('Hello, JavaScript!')"
Hello, JavaScript!

bun -e leverages Bun's builtin support for TypeScript & JSX:

$ bun -e "console.log(<div>Hello, TSX</div> as React.ReactNode);"
<div>Hello, TSX</div>

Top-level await also works:

$ bun -e "console.write(await fetch('https://api.github.com/repos/oven-sh/bun').then(r => r.text()))"
{
  "id": 357728969,
  "node_id": "MDEwOlJlcG9zaXRvcnkzNTc3Mjg5Njk=",
  "name": "bun",
  "full_name": "oven-sh/bun",
  "private": false,
  "owner": {
    "login": "oven-sh",
    "id": 108928776,
...

bun --env-file <path> overrides the .env file to load

Bun's automatic .env loader first landed nearly two years ago in v0.0.x, and today we're releasing support for --env-file to override which specific .env file to load. This is useful for testing different environments.

If you have custom environment variables that you want to load into your process, you can use --env-file to specify a file containing environment variables to load into the process. You can define multiple --env-file arguments.

bun --env-file=.env.1 src/index.ts
bun --env-file=.env.abc --env-file=.env.def run build

You can use --env-file when running scripts in bun's runtime, or when running package.json scripts.

Thanks to @otgerrogla for implementing this feature.

server.url

We added support for server.url, which returns a URL object that defines the location of the HTTP server. This is especially useful in tests when you want to get the formatted URL of the server.

const server = Bun.serve({
  port: 0, // random port
  fetch(request) {
    return new Response();
  },
});

console.log("Listening...", `${server.url}`);
// Listening... http://localhost:1234/

import.meta.env for accessing environment variables

You can now access environment variables using import.meta.env, a pattern used by frameworks such as Vite.

console.log(import.meta.env.NODE_ENV); // "development"

import.meta.env is an alias of process.env and Bun.env. It exists for compatibility with other parts of the JavaScript ecosystem.

Thanks to @otgerrogla for implementing this feature.

expect.unreachable()

bun test gets support for expect.unreachable(), which throws an error if the code path is reached. This is useful for ensuring that a function is never called.

import { expect, test } from "bun:test";
import { foo } from "my-lib";

test("enum handles every case", () => {
  switch (foo()) {
    case "a":
      break;
    case "b":
      break;
    default: {
      // Now:
      expect.unreachable();
      // Previously:
      // throw new Error("Unreachable");
    }
  }
});

Improved CLI help output

We've redone the help menus for Bun's CLI to make them easier to read and understand.

bun
install
build
test
run
bun
$ bun --help
Bun is a fast JavaScript runtime, package manager, bundler, and test runner. (1.0.12 (904134cc))

Usage: bun <command> [...flags] [...args]

Commands:
  run       ./my-script.ts       Execute a file with Bun
            lint                 Run a package.json script
  test                           Run unit tests with Bun
  x         prettier             Execute a package binary (CLI), installing if needed (bunx)
  repl                           Start a REPL session with Bun

  install                        Install dependencies for a package.json (bun i)
  add       astro                Add a dependency to package.json (bun a)
  remove    left-pad             Remove a dependency from package.json (bun rm)
  update    backbone             Update outdated dependencies
  link      [<package>]          Register or link a local npm package
  unlink                         Unregister a local npm package
  pm <subcommand>                Additional package management utilities

  build     ./a.ts ./b.jsx       Bundle TypeScript & JavaScript into a single file

  init                           Start an empty Bun project from a blank template
  create    @evan/duckdb         Create a new project from a template (bun c)
  upgrade                        Upgrade to latest version of Bun.
  <command> --help               Print help text for command.

Flags:
      --watch                    Automatically restart the process on file change
      --hot                      Enable auto reload in the Bun runtime, test runner, or bundler
      --smol                     Use less memory, but run garbage collection more often
  -r, --preload                  Import a module before other modules are loaded
      --inspect                  Activate Bun's debugger
      --inspect-wait             Activate Bun's debugger, wait for a connection before executing
      --inspect-brk              Activate Bun's debugger, set breakpoint on first line of code and wait
      --if-present               Exit without an error if the entrypoint does not exist
      --no-install               Disable auto install in the Bun runtime
      --install                  Configure auto-install behavior. One of "auto" (default, auto-installs when no node_modules), "fallback" (missing packages only), "force" (always).
  -i                             Auto-install dependencies during execution. Equivalent to --install=fallback.
  -e, --eval                     Evaluate argument as a script
      --prefer-offline           Skip staleness checks for packages in the Bun runtime and resolve from disk
      --prefer-latest            Use the latest matching versions of packages in the Bun runtime, always checking npm
  -p, --port                     Set the default port for Bun.serve
  -b, --bun                      Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)
      --silent                   Don't print the script command
  -v, --version                  Print version and exit
      --revision                 Print version with revision and exit
      --env-file                 Load environment variables from the specified file(s)
      --cwd                      Absolute path to resolve files & entry points from. This just changes the process' cwd.
  -c, --config                   Specify path to Bun config file. Default $cwd/bunfig.toml
  -h, --help                     Display this menu and exit

(more flags in bun install --help, bun test --help, and bun build --help)

Learn more about Bun:            https://bun.sh/docs
Join our Discord community:      https://bun.sh/discord
install
$ bun install --help
Usage: bun install [flags] [...<pkg>]
Alias: bun i
  Install the dependencies listed in package.json

Flags:
  -c, --config              Specify path to config file (bunfig.toml)
  -y, --yarn                Write a yarn.lock file (yarn v1)
  -p, --production          Don't install devDependencies
      --no-save             Don't update package.json or save a lockfile
      --save                Save to package.json (true by default)
      --dry-run             Don't install anything
      --frozen-lockfile     Disallow changes to lockfile
  -f, --force               Always request the latest versions from the registry & reinstall all dependencies
      --cache-dir           Store & load cached data from a specific directory path
      --no-cache            Ignore manifest cache entirely
      --silent              Don't log anything
      --verbose             Excessively verbose logging
      --no-progress         Disable the progress bar
      --no-summary          Don't print a summary
      --no-verify           Skip verifying integrity of newly downloaded packages
      --ignore-scripts      Skip lifecycle scripts in the project's package.json (dependency scripts are never run)
  -g, --global              Install globally
      --cwd                 Set a specific cwd
      --backend             Platform-specific optimizations for installing dependencies. Possible values: "clonefile" (default), "hardlink", "symlink", "copyfile"
      --link-native-bins    Link "bin" from a matching platform-specific "optionalDependencies" instead. Default: esbuild, turbo
      --help                Print this help menu
  -d, --dev                 Add dependency to "devDependencies"
      --optional            Add dependency to "optionalDependencies"
  -E, --exact               Add the exact version instead of the ^range

Examples:
  Install the dependencies for the current project
  bun install

  Skip devDependencies
  bun install --production

Full documentation is available at https://bun.sh/docs/cli/install
build
$ bun build --help
Usage:
  Transpile and bundle one or more files.
  bun build [...flags] [...entrypoints]

Flags:
      --compile                       Generate a standalone Bun executable containing your bundled code
      --watch                         Automatically restart the process on file change
      --target                        The intended execution environment for the bundle. "browser", "bun" or "node"
      --outdir                        Default to "dist" if multiple files
      --outfile                       Write to a file
      --sourcemap                     Build with sourcemaps - 'inline', 'external', or 'none'
      --format                        Specifies the module format to build to. Only "esm" is supported.
      --root                          Root directory used for multiple entry points
      --splitting                     Enable code splitting
      --public-path                   A prefix to be appended to any import paths in bundled code
  -e, --external                      Exclude module from transpilation (can use * wildcards). ex: -e react
      --entry-naming                  Customize entry point filenames. Defaults to "[dir]/[name].[ext]"
      --chunk-naming                  Customize chunk filenames. Defaults to "[name]-[hash].[ext]"
      --asset-naming                  Customize asset filenames. Defaults to "[name]-[hash].[ext]"
      --server-components             Enable React Server Components (experimental)
      --no-bundle                     Transpile file only, do not bundle
      --minify                        Enable all minification flags
      --minify-syntax                 Minify syntax and inline data
      --minify-whitespace             Minify whitespace
      --minify-identifiers            Minify identifiers

Examples:
  Frontend web apps:
  bun build ./src/index.ts --outfile=bundle.js
  bun build ./index.jsx ./lib/worker.ts --minify --splitting --outdir=out

  Bundle code to be run in Bun (reduces server startup time)
  bun build ./server.ts --target=bun --outfile=server.js

  Creating a standalone executable (see https://bun.sh/docs/bundler/executables)
  bun build ./cli.ts --compile --outfile=my-app

A full list of flags is available at https://bun.sh/docs/bundler
test
$ bun test --help
Usage: bun test [...flags] [<pattern>...]
  Run all matching test files and print the results to stdout

Flags:
      --timeout              Set the per-test timeout in milliseconds, default is 5000.
      --update-snapshots     Update snapshot files
      --rerun-each           Re-run each test file <NUMBER> times, helps catch certain bugs
      --only                 Only run tests that are marked with "test.only()"
      --todo                 Include tests that are marked with "test.todo()"
      --coverage             Generate a coverage profile
      --bail                 Exit the test suite after <NUMBER> failures. If you do not specify a number, it defaults to 1.
  -t, --test-name-pattern    Run only tests with a name that matches the given regex.

Examples:
  Run all test files
  bun test

  Run all test files with "foo" or "bar" in the file name
  bun test foo bar

  Run all test files, only including tests whose names includes "baz"
  bun test --test-name-pattern baz

Full documentation is available at https://bun.sh/docs/cli/test
run
$ bun run --help
Usage: bun run [flags] <file or script>

Flags:
      --silent               Don't print the script command
  -b, --bun                  Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)
      --watch                Automatically restart the process on file change
      --hot                  Enable auto reload in the Bun runtime, test runner, or bundler
      --smol                 Use less memory, but run garbage collection more often
  -r, --preload              Import a module before other modules are loaded
      --inspect              Activate Bun's debugger
      --inspect-wait         Activate Bun's debugger, wait for a connection before executing
      --inspect-brk          Activate Bun's debugger, set breakpoint on first line of code and wait
      --if-present           Exit without an error if the entrypoint does not exist
      --no-install           Disable auto install in the Bun runtime
      --install              Configure auto-install behavior. One of "auto" (default, auto-installs when no node_modules), "fallback" (missing packages only), "force" (always).
  -i                         Auto-install dependencies during execution. Equivalent to --install=fallback.
  -e, --eval                 Evaluate argument as a script
      --prefer-offline       Skip staleness checks for packages in the Bun runtime and resolve from disk
      --prefer-latest        Use the latest matching versions of packages in the Bun runtime, always checking npm
  -p, --port                 Set the default port for Bun.serve
      --main-fields          Main fields to lookup in package.json. Defaults to --target dependent
      --extension-order      Defaults to: .tsx,.ts,.jsx,.js,.json
      --tsconfig-override    Specify custom tsconfig.json. Default <d>$cwd<r>/tsconfig.json
  -d, --define               Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:"development". Values are parsed as JSON.
  -l, --loader               Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi
      --no-macros            Disable macros from being executed in the bundler, transpiler and runtime
      --jsx-factory          Changes the function called when compiling JSX elements using the classic JSX runtime
      --jsx-fragment         Changes the function called when compiling JSX fragments
      --jsx-import-source    Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: "react"
      --jsx-runtime          "automatic" (default) or "classic"
      --env-file             Load environment variables from the specified file(s)
      --cwd                  Absolute path to resolve files & entry points from. This just changes the process' cwd.
  -c, --config               Specify path to Bun config file. Default <d>$cwd<r>/bunfig.toml
  -h, --help                 Display this menu and exit

Examples:
  Run a JavaScript or TypeScript file
  bun run ./index.js
  bun run ./index.tsx

  Run a package.json script
  bun run dev
  bun run lint

Full documentation is available at https://bun.sh/docs/cli/run

Thanks to @colinhacks and @paperdave.

Macros now support builtin modules

You can now import builtin modules at bundle-time using macros in Bun.

Read a file into a string at bundle-time

// file.ts
import { readFileSync } from "node:fs" with {type: "macro"};

export const contents = readFileSync("hello.txt", "utf8");

Build:

bun build --target=browser ./file.ts

Spawn a process at bundle-time

// spawn.ts
import { spawnSync } from "node:child_process" with {type: "macro"};

const result = spawnSync("echo", ["Hello, world!"], {encoding: "utf-8"}).stdout;
console.log(result); // "Hello, world!"

Build:

bun build --target=browser ./spawn.ts

mock.module(...) now supports default exports

We fixed a bug preventing mock.module from overriding default exports.

import { mock } from "bun:test";

mock.module("node:fs", () => ({
  default: {
    readFileSync() {
      return "Hello, world!";
    },
  },
}));

import fs from "node:fs";

// Previously, this would have tried to read from the file system:
console.log(fs.readFileSync("hello.txt")); // "Hello, world!"

mock.module(...) now supports re-exports

We fixed a bug preventing mock.module from overriding re-exports.

// # c.test.ts
import { mock } from "bun:test";

mock.module("./a.ts", () => ({
  exportedFromB: 123,
}));

import { exportedFromB } from "./a.ts";

// # a.ts
import { exportedFromB } from "./b.ts";
export { exportedFromB };

// # b.ts
export const exportedFromB = 456;

// Previously, this would have returned 456:
console.log(exportedFromB); // 123

For ESM, this updates live-bindings. For CommonJS modules, the changes will apply the next time the module is require'd.

Fixed: transpiler preserves custom directives

Previously, if you had an unknown directive at the top of the file, Bun's transpiler would consider it dead code and remove it. This has been fixed.

"use client"; // <-- this line is preserved now

export const MyComponent = () => <div>Hello, world!</div>;

Fixed: Dirent.name setter must be a string

Previously, if you tried to create an instance of the node:fs module's Dirent class via Object.create(Dirent), accessing the name property would throw an error since it was not created via new.

import { Dirent } from "node:fs";

const dirent = Object.create(Dirent);
dirent.name = "hello.txt";
console.log(dirent.name); // "hello.txt"

Some libraries depend on this behaviour, and it has been fixed.

Fixed: Assertion failures and crashes

We recently made changes to our version of JavaScriptCore to ensure that assertions are enabled in debug builds. This allowed us to fix a wide range of bugs that were previously causing crashes or assertion failures. This will also help make Bun more stable going forward.

Some of the issues fixed include:

  • Potential crash with process.mainModule
  • Crash when constructing a Worker
  • Crash when calling Server.close()
  • Crash with Bun.serve() that could theoretically occur when an in-flight request is aborted
  • Assertion failure in Headers.prototype.getAll that theoretically could cause a crash
  • Assertion failure in module.require && module.reoslve that theoretically could cause a crash
  • Assertion failure in ServerWebSocket.close caused by incorrectly checking the types of arguments passed to the function
  • Assertion failure when rejecting a Promise that could cause a missing stacktrace
  • Assertion failure in the onerror callback on the global object that could lead to a crash

Fixed: potential crash in require() after exception is thrown

A crash that sometimes happened when a CommonJS module that was loaded via require() and threw an exception during initialization while nested inside many other modules that were also incompletely initialized has been fixed.

One way this crash manifested was a module requiring sharp when native addons were not installed and that module was nested inside many other modules that were also incompletely initialized.

What caused this?

The source code was garbage collected before the exception was thrown, and it was assumed that the source code could not be garbage collected at that point. We added the missing null check to fix this issue.

Fixed: ENOENT when using bun install + package-lock.json

We fixed an edge-case in bun install that would have caused a barrage of ENOENT errors. This would have occured when no bun.lockb existed, but a package-lock.json existed.

Fixed: bun install with a package that contains ~

We fixed a bug where bun install would fail if a package contained a ~ in the version. This has been fixed, thanks to @Hanaasagi.

Fixed: Bun does not forward arguments to lifecycle scripts

We fixed a bug where Bun would pass arguments to lifecycle scripts. For example, if you ran bun run build --production, Bun would pass --production to the prebuild script, which could cause unexpected failures.

package.json
{
  "scripts": {
    "prebuild": "echo \"Building...\"",
    "build": "bun scripts/build.ts"
  }
}

Before:

$ echo "Building..." --production
Building... --production

After:

$ echo "Building..."
Building...

Thanks to @nektro for fixing this issue.

Fixed: Hang in bun:sqlite's .get() method

Previously, the following code would potentially hang after being called about 5,000 times in quick succession:

import { Database } from "bun:sqlite";

const db = new Database(":memory:");

const query = db.prepare("SELECT 1");

for (let i = 0; i < 10000; i++) {
  query.get();
}

This has been fixed.

What caused this?

The prototype used for SQLite statements had an incorrect JSType specified.

JavaScriptCore has an internal JSType tag associated with each heap-allocated value. This tag is used in many places, including the JIT. The SQLite statement used a FunctionType instead of an ObjectType, which caused the DFG JIT to incorrectly optimize the .get() method. This led to a hang after the function was executed enough times for the DFG JIT to optimize the function.

JavaScriptCore had an assertion that caught this bug, but we weren't running JavaScriptCore with assertions enabled in development. To catch these kinds of bugs in the future, we've enabled assertions in the debug build of JavaScriptCore (the engine) to be used for the debug build of Bun.

We fixed an issue where headers.raw() from node-fetch would return the Set-Cookie header with the wrong capitalization.

Before:

import { Headers } from "node-fetch";

const headers = new Headers({
  "User-Agent": "Bun",
  "Set-Cookie": "foo=bar",
});
console.log(headers.raw());
// { "user-agent": "Bun", "Set-Cookie": ["foo=bar"] }

After:

import { Headers } from "node-fetch";

const headers = new Headers({
  "Set-Cookie": "foo=bar",
});
console.log(headers.raw());
// { "user-agent": "Bun", "set-cookie": ["foo=bar"] }

Thanks to @cirospaciari for fixing this issue.

Fixed: AsyncLocalStorage could not be disabled multiple times

There was a bug where AsyncLocalStorage could not be disabled multiple times. This has been fixed.

Before:

import { AsyncLocalStorage } from "node:async_hooks";

const store = new AsyncLocalStorage();

store.enterWith(1);
store.disable();
console.log(store.getStore()); // undefined

store.enterWith(2);
store.disable();
console.log(store.getStore()); // 2

After:

import { AsyncLocalStorage } from "node:async_hooks";

const store = new AsyncLocalStorage();

store.enterWith(1);
store.disable();
console.log(store.getStore()); // undefined

store.enterWith(2);
store.disable();
console.log(store.getStore()); // undefined

Thanks to @cirospaciari for fixing this issue.

Fixed: fs.read() returned incorrect value when promisified

We fixed a bug where fs.read() would return an incorrect value when util.promisify(fs.read) was used.

Before:

import fs from "node:fs";
import { promisify } from "node:util";

const read = promisify(fs.read);
const fd = await fs.promises.open("hello.txt");
const buffer = new Uint8Array(16);

const result = await read(fd, buffer, 0, 16, 0);
console.log(result); // 16

After:

import fs from "node:fs";
import { promisify } from "node:util";

const read = promisify(fs.read);
const fd = await fs.promises.open("hello.txt");
const buffer = new Uint8Array(16);

const result = await read(fd, buffer, 0, 16, 0);
console.log(result); // { bytesRead: 16, buffer }

Thanks to @nektro for fixing this issue.

Along the way, we also fixed:

  • fs.write() when promisified
  • fs.writev() when promisified
  • fs.readv() when promisified

Fixed: bug when missing certain headers in HTTP client responses

For HTTP/1.1, the Content-Length header and the Transfer-Encoding headers are both optional. When these headers are omitted, the HTTP client is supposed to wait until the connection is closed to determine the length of the response body (and implicitly, disable keep-alive).

Previously, Bun was not handling this case and treating these HTTP responses as malformed.

Now, Bun properly handles this scenario and disables keep-alive when the Content-Length & Transfer-Encoding headers are omitted. It is not recommended to omit these headers (keep-alive is an important optimization for HTTP/1.1 and particularly TLS), but Bun now handles this case.

Fixed: bun install --yarn would occasionally produce invalid YAML

There was a bug in Bun's yarn.lock file printer that would not properly escape identifiers in certain cases. This would cause bun install --yarn to produce a yarn.lock file that Yarn v1 was unable to parse. Note that technically yarn.lock is not a YAML file, it is a bespoke Yarn v1 specific format (but saying "YAML" is easier since it is very close to YAML).

Fixed: Allow crypto.subtle to be polyfilled

Previously, Bun would throw an error if crypto.subtle was polyfilled. There are some packages that depend on this behaviour, and it has been fixed.

Fixed: bun init would contain an invalid .gitignore

Ther was a bug where bun init would contain an invalid .gitignore file. This has been fixed thanks to @Pierre-Mike.

Fixed: crash in debugger

A crash could occur on start when using bun --inspect to debug an application, which has been fixed.

The crash was due to assuming a debugger was started when it might not have been started yet.

In certain cases, bun install could have binlinked the wrong version of a package. We previously were not bin linking into my-dependency/node_modules/.bin, preferring the root node_modules/.bin instead. This has been fixed, thanks to @dylan-conway.

Miscellaneous

We upgraded to a later version of Zig.

Thanks to 19 contributors!

You can also read the full changelog.