Bun - just another JS runtime?

Bun - just another JS runtime?

When the buzz about Bun started, I wondered, "Is this just another JS runtime trying to disrupt the JS ecosystem?" I just ignored it and never tried it out. But then the Twitter (call it X, whatever) posts were getting insane everyday and people were liking Bun more and more. I couldn't ignore it at this point, so I hesitantly tried it out. I was a little worried because it was just released back then and could cause some issues. I liked it! Specifically the speed part.

This article will look at speed comparisons of Bun, NPM and PNPM. We will also look at other things that Bun has to offer.

If you prefer video content, I have the same content uploaded as a video on YouTube.

Speed comparison

This section will compare the speeds of installing dependencies for a Vite TypeScript React app. It should be a little heavy, haha. I'm using the following command to create a new Vite application.

bunx create-vite@latest speeds --template react-ts

Feel free to use any package manager to create the project. The dependencies won't be installed; we need to install them manually.

Removing cache

Before proceeding, I want to remove the cache for PNPM and Bun.

pnpm store prune && bun pm cache rm

The above command should remove the cache for both of the package managers. So, all the packages that now get installed are not served through cache, that could provide an unfair advantage to a package manager.

Testing npm install speed

Use the following command to install dependencies using NPM and note down the time outputted.

time npm install

The time command helps you get the time it takes for a command to complete execution.

Testing pnpm install speed

Now, remove the node_modules folder generated by NPM using the following command (assuming you're on macOS or Linux):

rm -rf node_modules

Use the following command to install dependencies using PNPM and note down the time outputted.

time pnpm install

Testing bun install speed

Remove the node_modules folder generated by PNPM. Use the following command to install dependencies using Bun and note down the time outputted.

time bun install

Results are here

The time noted by me and you might vary on many many different factors, but I expect the order to remain the same. Following are my results.

  1. Bun: 6.890s

  2. PNPM: 14.213s

  3. NPM: 36.860s

Bun is the clear winner when it comes to speed! And it's not even close. If you're still using NPM, I don't know what you're waiting for. SWITCH ASAP!

Directly run TypeScript using Bun

Yes, you read the heading right- you can run TS directly with Bun. Okay, for developers who aren't nerds and have a social life, let me give a little context.

If you want to run a TypeScript file in Node.js, you can't directly do that. That's because Node.js doesn't understand TypeScript. It understands JavaScript. To run a "TypeScript" file, you need to transpile it to JavaScript using tsc and run the JS that's built after the transpilation process.

On the other hand, Bun transpiles the files automatically for you so that you don't need to transpile and run JS each time you make changes. In short, you can pass .ts files to Bun to run and Bun will not cry about it and do it's job.

You can have a file called as main.ts and have a simple console.log("Hello World"); in it and run the following command.

bun run main.ts

And the file will run, and you will see Hello World in your console. Convenient!

Inbuilt watch mode

Picture this: you're working on an Express server made using Node.js and want the changes to reflect immediately without manually restarting the server. What would you do? You would use nodemon!

nodemon main.js

The above command will run main.ts and watch for changes. If any changes are detected, the file will be run again with updated changes automatically.

What about TypeScript in the Node.js and Express case? You need to install ts-node the package in your dev dependencies, and you can do the following.

nodemon main.ts

However, there is a problem. If you are relying on fs and "current directory" by any chance, the current directory in the above case would be the place where the file is run. However, if you transpile the TS into JS, the current directory would be the build or dist directory, or whatever you've set in tsconfig.json file. It can cause a lot of confusion between development and production environments. Of course, you don't want to nodemon inside the production environment. To fix this, you must go fancy by watching the file changes, transpile TS to JS and nodemon-ing the built file.

BUT, what if I told you that when you're using Bun, you need not be bothered about all this at all? We already saw how you can run .ts directly using Bun. You DO NOT need a build or dist directory. Instead, while developing, you can do the following:

bun --watch main.ts

This will

  • Run the main.ts file.

  • Watch for any changes.

  • If there are any changes, restart the execution.

It's as simple as that. No nodemon, no transpilation, nothing. Since you don't need to transpile anything, you can better know the context of the current directory you will be mentioning in your code.

Cross-platform shell commands

A post by Jarred Sumner on the Bun blog explains why running shell commands is a pain in JavaScript, specifically Node.js. You must do a lot of stuff just to run a simple command in the shell. Well, you can read the blog for more information as to why it's such a pain in Node.js. Here, we will see how easy it is to run shell commands in Bun. Assuming you have a file main.ts and you want to run the ls command and log the contents of the command's response; you need the following code.

import { $ } from "bun";

await $`ls`;

That's three lines of codes, or two if we exclude the one empty line. It's that simple. If you want to get the command's response in a variable, the following is the code.

import { $ } from "bun";

const response = await $`ls`.text();

Yes, still a three line code.

But wait. (In Steve Jobs's style) There's ONE MORE THING!

Apple to unveil ARM-based Silicon in 'One More Thing' event; Production of  new Macs ramp-up | Smartprix.com

These shell commands are cross-platform. So, if you use a command like rmdir or basically any native command that is different for Windows and Linux, Bun converts those commands under the hood. So, there is no need to check the platforms and run different commands.

Accessing and writing files using Bun

Bun provides special methods for accessing and writing files.

const file = Bun.file(import.meta.dir + '/package.json'); // BunFile

const pkg = await file.json(); // BunFile extends Blob
pkg.name = 'my-package';
pkg.version = '1.0.0';

await Bun.write(file, JSON.stringify(pkg, null, 2));

In the above code, we are accessing the file using Bun.file() and converting it into an object and storing it into pkg. We are then modifying pkg and writing the updated package.json using Bun.write(). It's that easy.

Password hashing using Bun

Hashing and comparing passwords is easy with Bun.

const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh...

const isMatch = await Bun.password.verify(password, hash);
// => true

No third-party packages are required. Works out of the box. If you want to see how it works when someone inputs the wrong password, check out my video where I talk about it.

Conclusion

In my opinion, Bun is really cool. The last time I checked it on a project that was made using Node, it did have some issues running. But the team at Bun is shipping insanely (of course in a good way) and I think we will have nice things ahead of us. Also, they are working heavily on getting Bun on Windows without using WSL. So, all the Windows users out there, make sure you follow their Twitter account for more information.

I would appreciate any suggestions about my content in the comments.