Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

bun2nix

Bun2nix Logo

bun2nix is a fast Rust-based tool for converting lock-files generated by the JavaScript Bun (v1.2+) package manager to Nix expressions, which allows them to be consumed to build Bun packages reproducibly.

Advantages

Here are some of the advantages of using bun2nix over the alternatives:

  • Much faster than other similar lang2nix tools for the JavaScript ecosystem - a full install will only take around 50ms for a medium project with 2k packages
  • Build AOT-compiled binaries easily with Bun that fit the Nix model much better than NPM scripts
  • Quality error messages because of the static types in Rust

Alternatives

Here are some alternatives to bun2nix in the JavaScript ecosystem which fulfill a similar purpose:

Installation

Bun2nix is primarily distributed as a nix flake library that contains everything you need to package your bun application with the tool.

This can be set up in two ways:

From A Template

By default a handful of templates are offered for starting off a new project with bun2nix enabled by default:

  • A minimal hello world binary program - the default.
  • A basic react website and server setup.
  • And the rest in templates/

Notable files

The main files of note are:

  • flake.nix ⇒ Contains basic project setup for a nix flake for bun2nix
  • default.nix ⇒ Contains build instructions for this bun package
  • bun.nix ⇒ Generated bun expression from bun.lock
  • package.json ⇒ Standard JavaScript package.json with a postinstall script pointing to bun2nix

Default - Minimal Setup

To produce the default minimal sample, run:

nix flake init -t github:nix-community/bun2nix

This is a bare-bones project created via bun init which produces a simple hello world binary packaged via bun2nix.

React Website

To start with the React website template run

nix flake init -t github:nix-community/bun2nix#react

This is a simple example of a basic React app built through bun2nix.

In a pre-existing flake

To install bun2nix in an already existing bun project with a flake.nix, the following steps are recommended:

1. Source the bun2nix repo

Add the bun2nix flake to your inputs as follows:

bun2nix.url = "github:nix-community/bun2nix";
bun2nix.inputs.nixpkgs.follows = "nixpkgs";

1.5. (Optional) Use the binary cache

The bun2nix executable typically takes a while to compile, which is typical for many Rust programs, hence, hence, it may be a good idea to use the nix-community cache.

To add it include the following in your flake.nix.

nixConfig = {
    extra-substituters = [
      "https://cache.nixos.org"
      "https://nix-community.cachix.org"
    ];
    extra-trusted-public-keys = [
      "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    ];
};

2. Get a bun2nix binary

bun2nix has a cross-platform web-assembly based NPM package available for usage. This should be the default choice as it allows those who do not have nix installed to work on your project and keep the generated bun.nix file up to date. Simply install and run it with:

bun install --dev bun2nix
bunx bun2nix

Add the binary to your environment with nix

Alternatively, add the native bun2nix CLI program into your developer environment by adding it to your dev-shell.

devShells.default = pkgs.mkShell {
  packages = with pkgs; [
    bun
    bun2nix.packages.${system}.default
  ];
};

NOTE: the system variable can be gotten in a variety of convenient ways - including flake-utils or nix-systems.

3. Use the binary in a bun postinstall script

To keep the generated bun.nix file produced by bun2nix up to date, add bun2nix as a postinstall script to run it after every bun operation that modifies the packages in some way.

Add the following to package.json:

"scripts": {
    "postinstall": "bun2nix -o bun.nix"
}

4. Build your package with Nix

Finally, a convenient package builder is exposed inside bun2nix - mkDerivation.

Add the following to flake.nix:

A nice way to do this might be with the overlay

my-package = pkgs.callPackage ./default.nix {
    inherit bun2nix.packages.${system}.default;
};

And place this in a file called default.nix

{ bun2nix, ... }:
bun2nix.mkDerivation {
  pname = "bun2nix-example";
  version = "1.0.0";

  src = ./.;

  bunNix = ./bun.nix;

  module = "index.ts";
}

A list of available options for mkDerivation can be seen at the building packages page, along with other useful things for building bun packages.

Overlay

bun2nix provides an overlay which places the bun2nix binary (along with it's passthru functions) into pkgs for ease of use, where you can then use it in your devShell or with pkgs.callPackage, etc.

Usage

Set up the bun2nix overlay as follows:

Add the overlay

Instantiate pkgs with the bun2nix attribute included:

{ bun2nix, nixpkgs, ... }:
let
  pkgs = import inputs.nixpkgs {
    inherit system;
    overlays = [ inputs.bun2nix.overlays.default ];
  };
in
{ ... }

Use bun2nix from pkgs

Use your freshly overlaid pkgs to build a bun2nix project:

{ pkgs, ... }:
pkgs.stdenv.mkDerivation {
  pname = "react-website";
  version = "1.0.0";

  src = ./.;

  nativeBuildInputs = [
    pkgs.bun2nix.hook
  ];

  bunDeps = pkgs.bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };

  buildPhase = ''
    bun run build \
      --minify
  '';

  installPhase = ''
    mkdir -p $out/dist

    cp -R ./dist $out
  '';
}

Or, to add the bun2nix binary to your devShell:

# In your flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    bun2nix.url = "github:nix-community/bun2nix";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, bun2nix, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ bun2nix.overlays.default ];
        };
      in
      {
        devShells.default = pkgs.mkShell {
          packages = with pkgs; [
            bun
            bun2nix
          ];
        };
      }
    );
}

The Command Line Tool

The recommended way to use bun2nix is by adding it to your package.json to automatically run after any package management option:

"scripts": {
    "postinstall": "bun2nix -o bun.nix"
}

However, if you run without the -o flag it will produce text output over stdout similar to other lang2nix tools. Hence, if you enforce formatting rules in your repository, it is likely a good idea to pass it through a formatter before writing the file.

Choosing between the WASM CLI and the native CLI

You should use the WASM CLI if you are:

  • On a team in environments where Nix may not be installed (i.e. working with Windows users, etc.)
  • Not using any exotic dependency types (tarball, git, etc.)

You should use the native CLI if you are:

  • All using Nix anyway (less overhead because it is not ran in a JavaScript runtime)
  • Using dependency types which require a prefetch (tarball, git, etc.)

Options

WASM CLI

Currently, the options available from the web assembly command line tool are as follows:

Description
  Convert Bun (v1.2+) packages to Nix expressions

Usage
  $ bun2nix [options]

Options
  -l, --lock-file      The Bun (v1.2+) lockfile to use to produce the Nix expression  (default bun.lock)
  -o, --output-file    The output file to write to - if no file location is provided, print to stdout instead
  -v, --version        Displays current version
  -h, --help           Displays this message

Native CLI

Currently, the options available from the native command line tool are as follows:

Convert Bun (v1.2+) packages to Nix expressions

Usage: bun2nix [OPTIONS]

Options:
  -l, --lock-file <LOCK_FILE>      The Bun (v1.2+) lockfile to use to produce the Nix expression [default: ./bun.lock]
  -o, --output-file <OUTPUT_FILE>  The output file to write to - if no file location is provided, print to stdout instead
  -h, --help                       Print help
  -V, --version                    Print version

Building Packages

bun2nix provides a number of functions to aid in building Bun-related packages:

All of these functions are available as attributes on the bun2nix package itself via its passthru.

Building with bun2nix.mkDerivation

mkDerivation is provided as a library function to build Bun packages from the file generated by the bun2nix command line tool.

This function is intended for use building binaries with Bun. If you need to build something like a compiled React website, the more idiomatic choice is probably the bun2nix hook.

Example

Currently, basic usage would look something like:

{bun2nix, ...}:
bun2nix.mkDerivation {
  pname = "simple-bun-app";
  version = "1.0.0";

  src = ./.;

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };

  module = "index.ts";
}

or, in the more implicit style:

{bun2nix, ...}:
bun2nix.mkDerivation {
  packageJson = ./package.json;
  src = ./.;

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };
}

NOTE: building with the implicit package.json values makes a number of basic assumptions about your project that it expects to hold true in the name of convenience. Approximately, these are:

  • name is a field that is acceptable for use as the name of the binary produced by your package.
  • version is a field denoting your package version in proper semantic versioning.
  • module is a field pointing towards your index.ts file or equivalent. If you notice any strange errors while using the implicit build scheme try specifying the values manually and contribute a new descriptive assert message to mkDerivation.

Arguments

The full list of accepted arguments is:

Additionally, the full range of config options from the hook is available too.

ArgumentPurpose
packageJson(Optional) Your project's package.json. If supplied can be used to complete pname, version and module instead of requiring them manually.
pnameThe name of the package to build. Required if packageJson is not given.
versionYour package version. Required if packageJson is not given.
moduleThe index.{js,ts} file entry point to your Bun application. This should be a string containing the relative path from src. Required if packageJson is not given.

Building with bun2nix.hook

The bun2nix hook provides a simple way to extend an existing derivation with Bun dependencies by way of a setup hook.

This is especially useful for building things like websites or other artifacts that have a build artifact that is not an executable, or for building polyglot projects that need Bun dependencies on hand.

mkDerivation is a thin wrapper over this with stdenv.mkDerivation with some extra goodies for building Bun executables.

Example

You can use the bun2nix hook to integrate with an existing stdenv.mkDerivation style function by adding it to nativeBuildInputs like so:

{ stdenv, bun2nix, ... }:
stdenv.mkDerivation {
  pname = "my-react-website";
  version = "1.0.0";

  src = ./.;

  nativeBuildInputs = [
    bun2nix.hook
  ];

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };

  buildPhase = ''
    bun run build \
      --minify
  '';

  installPhase = ''
    mkdir -p $out/dist

    cp -R ./dist $out
  '';
}

Troubleshooting

The default behavior of bun2nix is to hard-link installs from the Nix store. Unfortunately, this is not guaranteed to work the same on all systems - if you see strange permissions errors from bun install try setting bunInstallFlags to --backend=symlink, which works but may be marginally slower.

Useful Functional Information

The bun2nix hook installs the fake Bun install cache created by fetchBunDeps at $BUN_INSTALL_CACHE_DIR.

This is then installed into your repo via a regular bun install during bunNodeModulesInstallPhase, which runs before the buildPhase.

Arguments

The full list of extra arguments bun2nix.hook adds to a derivation are:

ArgumentPurpose
bunDepsThe output of fetchBunDeps (or any other Nix derivation which produces a Bun-compatible install cache). This is required.
bunBuildFlagsFlags to pass to Bun in the default Bun build phase
bunCheckFlagsFlags to pass to Bun in the default Bun check phase
bunInstallFlagsFlags to pass to bun install. If not set these default to "--linker=isolated --backend=symlink" on aarch64-darwin or "--linker=isolated" on other systems
dontRunLifecycleScriptsBy default, after bunNodeModulesInstallPhase runs bun install --ignore-scripts, bunLifecycleScriptsPhase runs any missing lifecycle scripts after making the node_modules directory writable and executable. This attribute can be used to disable running bunLifecycleScriptsPhase
dontUseBunPatchDon't patch any shebangs in your src directory to use Bun as their interpreter
dontUseBunBuildDisable the default build phase
dontUseBunCheckDisable the default check phase
dontUseBunInstallDisable the default install phase

New Build Phases

The bun2nix hook introduces a number of new build phases which are worth knowing about:

These all have pre and post run hooks available

PhasePurpose
bunPatchPhaseBefore doing anything, patch shebangs of your local scripts to use Bun as their interpreter
bunNodeModulesInstallPhaseRuns bun install in your src repo
bunLifecycleScriptsPhaseRuns any Bun lifecycle scripts (i.e., "install", etc.) after making node_modules writable

Fetching with bun2nix.fetchBunDeps

fetchBunDeps is a handy function responsible for creating a bun compatible cache for doing offline installs.

Example

You should use fetchBunDeps in conjunction with the rest of bun2nix to build your Bun packages like so:

{
  bun2nix,
  ...
}:
bun2nix.mkDerivation {
  pname = "workspace-test-app";
  version = "1.0.0";

  src = ./.;

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };

  module = "packages/app/index.js";
}

Arguments

fetchBunDeps is designed to offer a number of flexible options for customizing your Bun install process:

ArgumentPurpose
bunNixThe bun.nix file as created by the bun2nix CLI
overridesAllows for modifying packages before install in the Nix store to patch any broken dependencies. See the overriding section below
useFakeNodeBy default, bun2nix patches any scripts that use Node in your dependencies to use bun as its executable instead. Turning this off will patch them to use node instead. This might be useful, if, for example, you need to link to actual Node v8 while building a native addon. Defaults to true.
patchShebangsIf scripts in your dependencies should have their shebangs patched or not. Defaults to true.

Overrides

fetchBunDeps provides an overrides api for modifying packages in the Nix store before they become a part of Bun's install cache and ultimately your project's node_modules.

You may want to use this to patch a dependency which, for example, makes a network request during install and fails because it's sandboxed by Nix.

Type

Each override attribute name must be a key that exists in your bun.nix file, and the attribute value must be a function that takes a derivation and returns another one.

Example

bunDeps = bun2nix.fetchBunDeps {
  bunNix = ./bun.nix;
    overrides = {
      "typescript@5.7.3" =
        pkg:
        runCommandLocal "override-example" { } ''
          mkdir $out
          cp -r ${pkg}/. $out

          echo "hello world" > $out/my-override.txt
        '';
    };
};

# Assertion will not fail
postBunNodeModulesInstallPhase = ''
  if [ ! -f "node_modules/typescript/my-override.txt" ]; then
    echo "Text file created with override does not exist."
    exit 1
  fi
'';

Operating Details

As mentioned above, the bun2nix.fetchBunDeps function produces a bun compatible cache, which allows bun to do offline installs through files available in the Nix store.

In general, for a given package, the installation process looks something like:

# bun.nix contents
"@types/bun@1.2.4" = fetchurl {
  url = "https://registry.npmjs.org/@types/bun/-/bun-1.2.4.tgz";
  hash = "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA==";
};

1. Download the package via a fetcher function

First, bun2nix deps are downloaded via a Nix FOD fetching function.

For most packages this is pkgs.fetchurl, and the hash can be taken directly from the bun.lock textual lock-file, meaning they don't need to be prefetched.

2. Extract the package

By default, fetching packages will produce either a tarball or a flat directory with the contents.

If the package is a tarball, it should be extracted first. Then, both types have their permissions normalized to make sure that scripts are properly executable/readable by the nixbld users.

Any references to node or bun binaries are also fixed up at this stage.

Unfortunately, NPM dependencies cannot use builtins.fetchTarball, which would do the fetching and extraction in one build step because it would produce a hash that differs from the one in the Bun lockfile.

3. Creating a cache entry

After this point, the packages are all the same shape (a patched flat directory). These are then passed into the bun2nix.cache-entry-creator binary, which creates symlinks to where Bun expects to find its packages in its cache.

This binary is a Zig-implemented command-line utility, because Bun uses a very specific version of Wyhash, which is vendored into their project, to patch package names before putting them into their cache.

If a package is in a sub-directory like @types, it must have appropriate parent directories created too.

# Input
"@types/bun@1.2.4"

# Output location
$out/share/bun-cache/@types/bun@1.2.4@@@1
# Input
"tailwindcss@4.0.0-beta.9"

# Output location
$out/share/bun-cache/tailwindcss@4.0.0-73c5c46324e78b9b@@@1

4. Forcing use of the cache

Our Bun cache is then forced to be used in the build process by the hook, which sets $BUN_INSTALL_CACHE_DIR to $out/share/bun-cache.

Building with bun2nix.writeBunApplication

writeBunApplication is provided as a library function as a convenient extension over mkDerivation. If you are looking to use writeBunApplication, please take a look over there first to familiarize yourself with its arguments.

This function is intended for use building non-compilable programs that run under bun.

Note: writeBunApplication takes heavy inspiration from pkgs.writeShellApplication. Think of it as an alternative which also manages bun dependencies.

When to use:

  • If you need something like a nextjs server that needs the original source directory and a valid node_modules to run
  • If you want to run a bunx binary as if it is a regular system executable with bun2nix managed dependencies.

When to avoid:

  • You should avoid this at all if you can bundle your application in a way where it produces a standalone artifact. This could be something like a website, a bun binary, or anything else that doesn't care about the original source. Use the hook or the mkDerivation to build those kinds of projects instead.

Example

Currently, basic usage would look something like (to package a nextjs server):

{bun2nix, ...}:
bun2nix.writeBunApplication {
  # Could also be `pname` and `version`
  # See the `mkDerivation` docs if this
  # looks unfamiliar to you
  packageJson = ./package.json;

  src = ./.;

  # Use the `build` script from
  # `package.json` instead of
  # bun's bundler accessed via
  # `bun build`
  #
  # Confusing, isn't it?
  buildPhase = ''
    bun run build
  '';

  # Start script to use to launch
  # your project at runtime
  startScript = ''
    bun run start
  '';

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };
}

Or, to package an arbitrary bunx binary (as long as it's in bun.nix somewhere)

{bun2nix, ...}:
bun2nix.writeBunApplication {
  packageJson = ./package.json;

  src = ./.;

  dontUseBunBuild = true;
  dontUseBunCheck = true;

  startScript = ''
    bunx cowsay "$@"
  '';

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };
}

Or, to package a simple web server that uses cowsay and a runtime environment variable:

# In your default.nix
{ pkgs, bun2nix, ... }:
bun2nix.writeBunApplication {
  pname = "cowsay-server";
  version = "1.0.0";

  src = ./.;

  startScript = ''
    bun run index.ts
  '';

  runtimeInputs = [ pkgs.cowsay ];
  runtimeEnv = {
    USER = "Nix";
  };

  bunDeps = bun2nix.fetchBunDeps {
    bunNix = ./bun.nix;
  };
}

And the corresponding index.ts:

import { $ } from "bun";

const user = process.env.USER || "World";

const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const message = `Hello, ${user}!`;
    const cowsay = await $`cowsay ${message}`.text();
    return new Response(cowsay);
  },
});

console.log(`Listening on http://localhost:${server.port} ...`);

Arguments

The full list of accepted arguments is:

Additionally, the full range of config options from mkDerivation is available too.

ArgumentPurpose
startScriptThe script to start your application with.
runtimeInputsRuntime nix dependencies your application requires. Defaults to [].
runtimeEnvRuntime environment variables. Defaults to {}.
excludeShellChecksshellcheck checks to exclude while checking your startScript. Defaults to [].
extraShellCheckFlagsExtra shellcheck flags to run when checking your startScript. Defaults to [].
bashOptionsBash Options to set for your script. Defaults to [ "errexit", "nounset", "pipefail" ].
inheritPathIf your script should use the external $PATH when begin ran. Defaults to false for purity.

Creating scripts with bun2nix.writeBunScriptBin

writeBunScriptBin is useful for creating once off $ Shell scripts, similarly to writeShellScriptBin in nixpkgs.

Example

An example bun script for printing "Hello World" might look like:

writeBunScriptBin {
  name = "hello-world";
  text = ''
    import { $ } from "bun";

    await $`echo "Hello World!"`;
  '';
};

Arguments

The full list of accepted arguments is:

ArgumentPurpose
nameThe name to give the binary of the script
textTextual contents of the script

V2 Release Guide

bun2nix V2 comes with many improvements, including:

  • Guaranteed correct bun installs via building the bun cache instead of creating the node_modules/ directory manually
  • Faster incremental builds via better caching
  • Rewritten nix API designed to be both more idiomatic and flexible
  • New NPM packaged web assembly CLI - run the bun2nix CLI with nothing but bunx bun2nix - see the NPM page.
  • Now supports more of bun's dependency types, including:
    • Files and symlinks
    • Tarballs
    • Git Dependencies

This work was done under Fleek. Please, check us out, especially if you are interested in Nix!

Updating

Below is a guide to all the breaking changes:

mkBunDerivation

  • mkBunDerivation has been renamed and moved from inputs.bun2nix.${system}.mkBunDerivation to inputs.bun2nix.packages.${system}.default.mkDerivation (or more simply bun2nix.mkDerivation)
  • The index attribute has been renamed to module to be inline with how it's defined in packages.json.
  • Instead of specifying bunNix directly, specify bunDeps using fetchBunDeps instead.
  • See mkDerivation for other new features and examples

mkBunNodeModules

  • mkBunNodeModules has been removed entirely in favor of fetchBunDeps to build a bun compatible cache.
  • Still need to add node_modules/ to an arbitrary derivation? Have a look at the new bun2nix hook, which provides a much nicer API.

writeBunScriptBin

  • This has been moved from inputs.bun2nix.lib.${system}.writeBunScriptBin to inputs.bun2nix.packages.${system}.default.writeBunScriptBin

New bun.nix schema

  • Now consumable by pkgs.callPackage and bun2nix.fetchBunDeps.
  • Don't forget to rewrite your bun.nix file by running bun2nix -o bun.nix before use!