Is Bun.js Yet Another JavaScript Runtime?

The JavaScript ecosystem is considered one of the largest among programming languages, boasting a wide range of tools and frameworks. However, executing JavaScript requires a runtime like Google's browser-based V8 Engine, Node.js, and Deno. The use of interpreters hinders the performance of interpreted languages compared to their compiled counterparts. Thus, the same piece of software written in Go, Rust, or C/C++ will likely be an order of magnitude faster than JavaScript or Python.

Bun.js is the new player on the JavaScript runtimes court, claiming to dethrone Node.js in performance while providing access to Node APIs and natively supporting TypeScript and JSX, surpassing Deno. Moreover, it implements the module resolution algorithm, allowing it to seamlessly support NPM packages, installed with Bun CLI's command bun install for 10x speed.

Is Bun.js "yet another JavaScript runtime"? Does it have the potential to become one of the big players? We will try to address these questions in the forthcoming sections.

What Exactly is Bun.js?

According to the creator of Bun.js, Jarred Sumner:

Bun is a modern JavaScript runtime like Node or Deno. It was built from scratch to focus on three main things:

  • Start fast (it has the edge in mind).
  • New levels of performance (extending JavaScriptCore, the engine).
  • Being a great and complete tool (bundler, transpiler, package manager).

Bun.js is designed to be a drop-in replacement for Node.js, removing the need for Webpack, Babel, and the TypeScript compiler as they are builtin in the runtime. The creators state that the goal of creating Bun.js is to boost JavaScript developers' productivity through a better and simpler toolchain while ensuring higher performance during build-time and execution time.

Bun.js is built upon JavaScriptCore, the engine powering Apple's Safari, extended with functionality like TS/JSX transpilation, SQL client, HTTP client, ...etc. These functionalities are implemented mainly in Zig, an imperative, general-purpose, statically typed, compiled system programming language, giving it a performance edge over the JavaScript-based Node.js.

Installation and Usage

According to Bun.js' official website, Bun.js is only available on UNIX-based operating systems such as macOS, Linux, And Windows Subsystem for Linux (WSL).

To install Bun.js, simply download the install script and run it with Bash. Following is a one-line terminal command that does all the work for you

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

The script will automatically unpack Bun.js' binaries and files on $HOME/.bun and add the path to the bun binary file to your user's PATH variable. You can test your Bun.js installation by opening a new terminal window/tab and typing bun. The output should be similar to the image below.

Bun.js v0.1.0 terminal output

Is Bun.js Yet Another JavaScript Runtime?

As of the time of writing this blog post, Bun.js is still in its beta stage at version v0.1.0, so one can expect that Bun.js would be a tad better in an official release. With that being said, test results should be taken with a grain of salt as comparing beta software against ones that battled for long years is rather "unfair".

Developer Tooling Test

To be as comprehensive and as concise as possible, we used two applications that cover pretty much all aspects of Bun.js:

  1. A one-page React app with simple CSS styling
  2. An HTTP server with only a home route handler /

React application

Usually, bootstrapping a new React application is done "Create React App" script. The result is a React template with a preconfigured toolchain of Webpack, Babel, SASS, and (optionally) TypeScript. With Bun.js, Create React App will configure Bun's built-in tooling instead of adding Webpack. The result is a very slim node_modules folder with a dozen dependencies.

Bootstrapping a Create React App is as simple as running the command:

$ bun create react ./my-app  # ./my-app is your application folder

You will notice that NPM's package-lock.json and YARN's yarn.lock are replaced with bun.lockb. The file is in binary format and thus not human readable (Yep, you can't check your dependencies on Pull Requests).

The current version of Create React App for Bun.js does not support TypeScript templates, hence you will have to manually add a tsconfig.ts, transform .js/.jsx files to .ts/.tsx files, and include @types/react and @types/react-dom to your development dependencies:

$ bun tsc --init    # generates default tsconfig.ts config file
$ bun add -d @types/react @types/react-dom

Now, you can start editing your React application and serving it in development environment using the following command:

$ bun dev

From a developer experience standpoint, starting a React application using Bun and CRA is as easy as it is with NPM and YARN. Integrating TypeScript (and/or TailwindCSS) however is quite the task, and hot reload does not refresh the browser, although these are all anticipated changes in future versions.

It is worth mentioning that initializing a CRA application and installing @types/react and @types/react-dom took much less time than usingnpmoryarn`.

HTTP Server

Bun.js provides built-in support for HTTP servers; all you have to do is export an object with port and fetch handler as default. For this example, we created a server that interacts with a SQLite3 database; It inserts a new post on POST /posts and gets all saved posts on GET /posts.

// server.ts

// initialize a local SQLite database
import { Database } from  "bun:sqlite";

const db = new  Database("mydb.sqlite");
db.run(
    `CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title VARCHAR(255) NOT NULL,
        description TEXT)`
);

PORT = 5000;

console.log(`Bun is listening on port ${PORT}`);

export  default {
    port: 5000,
    async  fetch(request: Request): Promise<Response>  {
        const path = request.url.split("/").pop()!;

        // GET /posts
        if (path === "posts"  && request.method === "GET") {
            const posts = db.query("SELECT * FROM posts").all();
            return  new  Response(JSON.stringify(posts));
        }

        // POST /posts
        if (path === "posts"  && request.method === "POST") {
            const { title, description } = await request.json();

            if (!title) {
                return  new  Response("title is required", { status: 400 });
            }

            if (!description) {
                db.run("INSERT INTO posts (title) VALUES (?)", title);
            } else {
                db.run("INSERT INTO posts (title, description) VALUES (?, ?)",
                        title,
                        description);
            }

            return  new  Response("Written to SQLite!", { status: 201 });
        }

        // Route not found
        return  new  Response(`Cannot ${request.method} /${path}`, { status: 404});
    },
};

Note that the server is written in a single TypeScript file server.ts, and it can be executed by running bun run server.ts with 0 dependencies required.

This may not be the de-facto way to create HTTP server with Bun.js in the future, but the simplicity of the built-in server will certainly allow the creation of elegant, production-ready, performant server libraries.

Performance Test

To discover whether Bun.js is worth the hype it is getting (or it is "yet another JavaScript runtime"), we will test its performance concerning your typical JavaScript developer's daily activities: downloading and installing packages, building and serving React applications, as well as testing the general performance of an HTTP server.

This example will depend on a single Linux-based machine with the following specifications:

  • Intel i7-5300U @2.7GHz clock speed with 2 cores, 4 threads,
  • 16Gb DDR3 RAM
  • 420Gb SSD Storage

Package Installation

In this section, we will compare Bun.js package manager against NPM and YARN. We will attempt to download 5 of the most popular packages (according to [npm rank] (gist.github.com/anvaka/8e8fa57c7ee1350e3491) gist) which are lodash, express, react, typescript, and jquery

We will use npm v6.14.17, yarn v1.22.18, and bun v0.1.0 throughout the test. Time taken by both npm and yarn is calculated by taking the mean value of 10 executions, whereas for bun the first execution and later execution (which depends on cache) will be taken instead.

lodash (24.5kB minified + gzipped)

Bun.js significantly outperforms NPM and YARN when installing lodash, as it takes less than half the time taken by npm. Furthermore, Installing it from the cache reduces the installation time to as little as 31ms!

lodash

express (229kB minified + gzipped)

yarn outperformed both Bun.js and npm in express with a time of ~1.5s. However, the cached version of express installed by bun takes as little as 29ms to install.

express

React (2.5kB minified + gzipped)

npm took the least time for installing the bare React library. Yet again, bun installs it in ~20ms from cache

react

TypeScript (807.8kB minified + gzipped)

bun outperformed both npm and yarn here, and its cache version takes again epsilon time.

typescript

jQuery (30.3kB minified + gzipped)

bun is still the winner, both in its over-the-internet method or from-cache method.

jquery

React Application

In this part, we test the time to start a React application in development mode. We will be using the Create React App template and run it using Node.js + Webpack and of course Bun.js.

It is worth noting that bootstrapping a Create React App with Bun.js takes significantly less time than using NPM or YARN (8s compared to 2m14s). But again, Bun.js depends on its internal tools and skips installing a hefty amount of dependencies. The decision whether to depend on Bun.js toolchain or use the battle-tested ones comes to you, the developer.

Development Server

It is publicly known that running a React application in development mode may take quite the time using Node.js + Webpack. With a Create React App application, Node.js + Webpack took ~12s to prepare and serve the app in development mode. Whereas, Bun.js started the server instantly! Jarred Sumner says in his Tweet about Bun vs Webpack that:

With Bun, Create React App starts 31x faster end-to-end.

Testing Bun.js with a big React project is outside the scope of this blog post. However, we encourage readers to try Bun.js with their own React applications and post their results in the comment section down below.

HTTP Server Average Response Time

For this test case, we will use the Bun.js server created before, alongside copies written to target both Node.js and Deno.

We will run all three servers on a local machine, and use hey load testing tool in 3 settings:

  • 1000 requests with 10 concurrent calls
  • 10000 requests with 100 concurrent calls
  • 100000 requests with 500 concurrent calls
1000 requests - 10 concurrent calls

10-1000-merger Bun.js takes less time handling the small load, with an average response time of ~0.4ms and at the peak of ~2ms for the least fortunate 5% of requests.

10-1000-reqsec Bun.js is superior to other runtimes by an order of magnitude, handling approximately x5 more requests per second than Node.js, and x3 more than Deno.

10000 requests - 100 concurrent calls

100-10000-merger Bun.js can handle weight at medium load, with an average response time of ~3.2ms and at the peak (surpassing both Node.js and Deno), and ~8.5ms for the 95 percentile, tying up with Deno.

100-10000-recsec

Bun.js still has a clear advantage at concurrency; handling ~x4 more requests per second than Node.js, and roughly x1.5 more than Deno.

100000 requests - 500 concurrent calls

500-100000-merger Bun.js stands as the best in heavy loads; averaging ~15ms and at the peak (beating both Node.js and Deno )of ~30ms for the 95 percentile (on par with Deno).

500-100000-recsec

Again, Bun.js is king when it comes to concurrency with x3.5 more requests handled per second than Node.js, and x1.5 more than Deno.

Conclusion

Bun.js, at its beta stage, stood firmly against Node.js and Deno, and has (fairly) simplified the process of development (although there is plenty of room for improvement). It has potential to be among the giants, given a clear roadmap, community adaptation and sponsoring.

So, is Bun.js yet another JavaScript runtime ? only time would tell...

For more articles, tutorials and news about open-source projects, visit my blog at devdog.co

Did you find this article valuable?

Support Houssem Eddine Zerrad by becoming a sponsor. Any amount is appreciated!