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.
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:
- A one-page React app with simple CSS styling
- 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 using
npmor
yarn`.
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!
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.
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
TypeScript (807.8kB minified + gzipped)
bun
outperformed both npm
and yarn
here, and its cache version takes again epsilon time.
jQuery (30.3kB minified + gzipped)
bun
is still the winner, both in its over-the-internet method or from-cache method.
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
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.
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
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.
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
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).
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