It seems like every few years, a new JavaScript tool gets everyone excited before the hype calms down. This time, it's Bun's turn. Bun is a different kind of solution to help JavaScript and TypeScript developers, but is it as revolutionary as Tweets suggest? Let's take a look!

JavaScript Tooling

Whether you're a full-stack, back-end or front-end developer, you've probably been touched by Node.js and NPM. Although Node.js represents a great platform for building server-side JavaScript, to me, the real benefit of it all is the rich ecosystem of tooling that I've become accustomed to. This includes things like Gulp, Vite, TailwindCSS, etc. For client-side development, you'd be hard pressed to find a project that doesn't use some sort of Node.js-driven tooling.

For many of us, this is fine. But there are flies in the ointment. First of all, the package management system and ecosystem are brilliant. But the npm itself can be slow to install and manage packages. That's why there have been alternatives like yarn and pnpm. These projects were meant to improve the project. Because they're essentially identical to how npm works (at least on the surface), they've been great to improve performance of installation and managing of packages.

On the server front, Node.js is becoming a bit long in the tooth. Node.js has been slower to move to ECMAScript modules that some might prefer. And there are some performance issues. For most users, Node.js is plenty fast, but for some, they wanted something faster. Deno was created as a new runtime for Node.js projects. Although it hasn't had the kind of adoption that some predicted, it still represented a direction that the community was headed for. It focused on compatibility with a more efficient runtime execution.

For client-side development, one of the categories that's been so important are ways to make JavaScript itself more efficient for the browser. These tools include transpilers and bundlers. Projects like Webpack, Rollup, Browserify, and others have the aim to process JavaScript (or TypeScript) into efficient packages for web-based projects. Early reasons for this were to allow web developers to take advantage of later versions of JavaScript while compiling down for earlier versions that were supported in the browser. These tools also break down JavaScript projects into all the code that's actually used (e.g., tree-shaking), minify the code, and break the code into lazily loaded modules. These have been spectacularly successful. They've changed the way that we deploy web projects.

Keeping all that in mind, why did Bun get created?

What Is Bun?

The Bun tooling aims to create a tool to handle the four main scenarios for JavaScript tooling:

  • JavaScript runtime
  • Package management
  • Bundler
  • Test runner

The stated goals of the new runtime include:

  • Speed
  • Execute TypeScript and JSX/TSX without having to transpile them first
  • ESM and CommonJS compatibility
  • Baked-in web browser APIs, like fetch and websockets
  • Complete Node.js/NPM compatibility

Bun claims to be four times faster than Node.js. For server-side projects like Node.js, server-side rendering, and tooling, that speed improves all parts of the development process. Being able to rely on your same code (with Node.js compatibility) and get a four-times speed increase is a big claim.

Bun uses the runtime from Apple's Safari, so some features directly use implementations of this runtime.

If all of this is true, you can start to see why Bun has gotten so much hype on release. My experience (mostly client-side development) is that the tooling lives up to the hype. To be clear, this is a 1.0 release. I'm not sure I'd move everything I do to Bun until some early adopters help sand down some rough edges. But if you want to get your hands dirty, let's get it installed.

Installing Bun

Are you on Mac or Linux? Then you're fine. But for Windows users, there's some bad news. At the time of writing this article, Bun doesn't work on Windows. This is on their roadmap. Although you can build from source on Windows, only the engine is supported at this time.

In version 1.x, Bun doesn't support Windows. WSL gives you an opportunity to try it out.

Instead, you can take a look at how it works by using Windows Subsystem for Linux (WSL). The installation instructions here are for Linux/Mac/WSL. They should be identical. To get started, open a console. In order to use the installer, you must have unzip installed. You can get started; just install unzip:

$ sudo apt-get install unzip

Once that's installed, you can install it with curl:

$ curl https://bun.sh/install | bash

That's it. You can check to see if it's installed by using:

$ bun -v 
1.0.7

If you can see the version, you're all set. Let's see how you could use Bun.

Using Bun

Now that you have Bun installed, you want to use it for work. Let's walk through an example.

Creating a Project

Bun doesn't require anything special for creating a new project. It has the equivalent of npm init called bun create:

$ bun create astro

What it really does is try to call:

$ bunx create-astro

The bunx command is the equivalent of npx from npm. This means that bun create adopts most of the create project systems that are already being used with npm and yarn. This means that you can create common project types, like so:

$ bun create vue
$ bun create react-app
$ bun create svelte
$ bun create astro

To be clear, this isn't as much about Bun supporting these frameworks, but, instead, it's about it leveraging what you're already doing. You don't have to create your projects with Bun in order to use Bun. You'll see later on in this article how to use it with existing projects.

You don't have to create your projects with Bun in order to use Bun.

Managing Dependencies

One of the places I see myself using Bun a lot is to manage dependencies. The speed of it makes it really compelling for larger projects. Although this is a one-time thing for a lot of cases, I could see it being used in CI servers to improve build performance. For example, for a standard Vue app, you can just use bun install (or just bun i):

$ bun install
bun install v1.0.7 (b0393fba)
 + @tsconfig/node18@18.2.2
 + @types/jsdom@21.1.4
 + @types/node@18.18.7 (v20.8.9 available)
 + @vitejs/plugin-vue@4.4.0
 + @vitejs/plugin-vue-jsx@3.0.2
 + @vue/test-utils@2.4.1
 + @vue/tsconfig@0.4.0
 + jsdom@22.1.0
 + npm-run-all2@6.1.1
 + typescript@5.2.2
 + vite@4.5.0
 + vitest@0.34.6
 + vue-tsc@1.8.21
 + pinia@2.1.7
 + vue@3.3.7
 + vue-router@4.2.5

I especially like that it shows (in a concise way) what packages were installed (as well as if newer versions are available). Instead of a package lock file that npm creates, it has its own lock format, as shown in Figure 1.

Figure 1: The Bun lock file
Figure 1: The Bun lock file

This behavior is identical to how yarn and npm work. You can install a new package like so:

$ bun i tailwindcss autoprefixer postcss
bun add v1.0.7 (b0393fba)
   installed tailwindcss@3.3.5 with binaries:
      - tailwind
      - tailwindcss
   installed autoprefixer@10.4.16 with binaries:
      - autoprefixer
   installed postcss@8.4.31
 68 packages installed

One thing to note is that the install behavior is for compatibility with npm; in Bun you'd normally use bun add (or just a):

$ bun add tailwindcss autoprefixer postcss
$ bun a tailwindcss autoprefixer postcss

I like that they've separated these processes into two named subcommands.

To remove a package, you can use the bun remove (or bun rm) command:

$ bun remove postcss
$ bun rm postcss

And to update package or packages, the bun update command:

$ bun update
$ bun update tailwindcss

You can update the project or individual packages. Note that bun update and bun upgrade are completely different commands. The bun upgrade command is specifically about upgrading Bun itself.

Developing with Bun

When working with Bun, it's the runtime that can be most helpful. Like npm, bun can use scripts in your package.json file and allow you to simply run the dev script:

$ bun run dev

On the face of it, it should be this simple. But for some project types, at least for now, you'll need to force the tooling to use bun. For example, any projects that use Vite for development will need to use bunx to make Vite use Bun's JS runtime. You can do this by adding the –bun flag;

$ bunx --bun vite

I usually just hide this in the package.json's scripts:

{
    "name": "bun-oven",
    "version": "0.0.0",
    "private": true,
    "scripts": {
        "dev": "bunx --bun vite",
...

Although there's a performance benefit in doing this because of Bun's improved execution, with some tools that are so fast already (e.g., Vite), the difference is not noticeable. Of course, as you scale up, this can really be seen. For example, in a large Next or Nuxt project, the faster runtime should be really apparent. But in a hello world Vue app, you'll never notice the difference.

Running Tests

The bane of some front-end developers is the speed of test runners. Don't get me wrong, test runners are doing a lot of work to find and run your tests. This is one area where Bun wants to improve. To do this, they implemented a test runner directly in the tool.

Out of the box, Bun can execute tests that use jest-style tests. For example, a simple test might be:

const counter = require('./counter');
describe("simple tests", () => {
    test('test increment', () => {
        const beforeCount = counter.count;
        counter.increment()
        expect(counter.count)
            .toBe(beforeCount + 1);
    });
    test('test double', () => {
        counter.increment()
        expect(counter.doubleCount())
            .toBe(counter.count * 2);
    });
});

Testing this with jest is as simple as just calling jest:

$ jest

With Bun, you can just use bun test. No dependencies are needed:

$ bun test

In my testing, bun test was substantially faster:

> jest
 PASS  ./counter.test.js (5.568 s)
  simple tests
    ? test increment (6 ms)
    ? test double
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.624 s
Ran all test suites.

Compared to:

bun test v1.0.7 (b0393fba)
counter.test.js:
? simple tests > test increment [0.03ms]
? simple tests > test double [0.02ms]
 2 pass
 0 fail
 2 expect() calls
Ran 2 tests across 1 files. [30.00ms]

These times were pretty consistent, although I'm running these in WSL and on a Windows drive, which can skew the numbers. I expect that the difference is more to do with discovery and execution speed. With a large number of tests, the startup and discovery time improvement would likely be less important to overall running of tests.

Is It Ready?

This is the big question. This article was written with 1.0.7, so still an early release build. I'm curious about how serious they are to reach a Windows build that's reliable.

In my testing, Bun still has some major compatibility issues with existing projects. For simple JavaScript, it worked pretty flawlessly. But introduce Vite, Vue, React, Next, or Nuxt, and issues surface pretty quickly. For example, when I tried to write tests inside of a Vue project, it threw errors that seemed to be as new as two weeks ago.

My advice is that it's worth keeping an eye on it. For most developers, the hype is bigger than it should be. I look forward to a sizable release at some point where I'd start to want to use it. If you're already using other tooling (e.g., Yarn, pnpm, or non-Jest testing), the benefits aren't great right now.

Where Are We?

The hype-train on Bun is both overblown and somewhat justified. Some of the directions it's taking could improve the development experience. The improvements to the runtime might be the real benefit, especially for server-side JavaScript. But for front-end developers, I'd hold off for now. Even the improvements with dependency management (e.g., bun install) aren't a big enough win for me to be able to recommend it to everyone.