Diving Deep into the World of Bun.js

calendar-emoji

26 Sept. 2023

JavascriptBun

The date is September 8, 2023 and Bun is fresh out of the Oven! The arrival of Bun v1.0 has shaken the whole JavaScript community and all of us are aboard the hype train.

Why such hype around Bun?

If we refer to the official docs then it says:

Bun is an all-in-one toolkit for JavaScript and TypeScript apps. It ships as a single executable called Bun

But what does being an all-in-one toolkit mean exactly and what makes it better than the existing JavaScript frameworks?

As stated by the developers:

The JavaScript ecosystem has become unnecessarily complicated, most code we write today has to pass through different plugins, bundlers, transpilers before it is even executable by Node.js

  • Simply put, Bun aims to reduce this complexity by providing an all-in-one solution thus improving performance manifold. From its versatile toolkit the feature that takes the spotlight is the Bun Js runtime, which comes as a drop-in replacement for Node.js.
  • Other than this it also includes a native Bundler, Transpiler, Package manager and even its own Test Runner!

Now let's break down all these things one at a time:

JavaScript Runtime

A runtime is simply an environment which provides all the necessary components to run a JavaScript program. It makes use of the JavaScript Engine which basically converts JavaScript code to machine code for the runtime to execute.

  • Bun boasts of incredible performance over Node.js thanks to its JavaScriptCore Engine developed by Apple for Safari which offers faster start times as compared to the V8 Engine used by Chrome which prioritizes faster execution time.

Transpiler

  • Node.js does not natively support .ts files, which means TypeScript files have to be transpiled into corresponding JavaScript code to execute using third party libraries.
  • Meanwhile Bun comes with an integrated transpiler that means it can natively support .ts, .js, .jsx and even .tsx files. This also results in much faster performance than Node.js

ES Modules & Common JS

  • Common JS is the original way of handling JavaScript code segments in Node.js which uses require() and module.exports for importing and exporting code respectively but is limited by the fact that it is synchronous.
  • On the other hand ES modules is the modern way of reusing JavaScript code. It uses import and export statements which also support asynchronous module handling.
  • Bun again stands out here as it provides compatibility to both Common JS and ES modules in the same file which is not possible in Node.js

Bundler

  • In large web applications, code is typically organized into multiple files and directories to improve maintainability and modularity. Bundlers help bring all these individual files together into a single or a few bundles that can be efficiently loaded by web browsers.
index.tsx

import * as ReactDOM from 'react-dom/client';
import {Component} from "./Component"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component message="Sup!" />)
component.tsx

export function Component(props: {message: string}) {
  return <p>{props.message}</p>
}

Using bun build ./index.tsx --outdir ./out where --outdir specifies the output directory for the bundled file. The output file looks something like this:

out/index.js


// Component.tsx
function Component(props) {
  return $jsxDEV("p", {
    children: props.message
  }, undefined, false, undefined, this);
}

// index.tsx
var rootNode = document.getElementById("root");
var root = $createRoot(rootNode);
root.render($jsxDEV(Component, {
  message: "Sup!"
}, undefined, false, undefined, this));
  • Bundlers help manage dependencies by bundling them together, resolving conflicts, and ensuring that the correct versions are included in the bundle.
  • Until the advent of Bun, Node.js applications relied on third-party libraries such as Parcel, Webpack, Rollup etc. to for common bundling tasks like minifying code, file conversions and code splitting.
  • Bun eliminates this need by bringing all these features under its integrated bundler which works for both TypeScript and JavaScript applications.

Bun Roadmap

Bun is a project with an incredibly large scope and is still in its early days. Long-term, Bun aims to provide an all-in-one toolkit to replace the complex, fragmented toolchains common today: Node.js, Jest, Webpack, esbuild, Babel, yarn, PostCSS, etc.

Ref.: https://bun.sh/docs/project/roadmap

Installation & Setup

  • Bun only has limited Windows support right now and can only be installed using Windows Subsytem for Linux(WSL) by enabling it. Meanwhile it is fully available for macOS and Linux systems.
  • For the purpose of this blog I'm running Manjaro OS on Vmware which is an Arch-based Linux distribution.
  • For Mac & Linux Bun can be installed using npm or curl. I already have npm installed so I'll just go ahead with npm install -g bun

Starting a project

bun init will initialize a project with some essential files, also instead of default entry point index.ts we will use index.js.

To test our runtime we can simply run bun run index.js and we get Hello via Bun! as the output.

Adding Modules/Packages

  • According to official docs bun install installs packages 20-100 times faster than npm install on Linux.
  • Either use bun install to install all existing packages from the package.json or bun add to add packages to your project.

File Imports & Exports

  • Even though Common JS modules are discouraged, Bun JS supports both import and require() and can be used in the same file as well, something which is not natively supported in Node.js.
  • Bun JS also supports custom path re-mapping, this allows to shorten paths by renaming and reusing them which is another major advantage over Node.js which does not support any path re-mapping.

Ref.: https://bun.sh/docs/runtime/modules#importing-packages https://bun.sh/guides/runtime/tsconfig-paths

tsconfig.json

{
  "compilerOptions": {
    "paths": {
      "my-custom-name": ["zod"],
      "@components/*": ["./src/components/*"]
    }
  }
}
import { z } from "my-custom-name"; // imports from "zod"
import { Button } from "@components/Button"; // imports from "./src/components/Button"

Instead of importing components from "./src/components/" everytime we can simply map a name "@components" to this path and reuse this to write neat code. Also we can use rename a path by mapping it to a custom name as shown above.

Environment Variables

  • Bun reads all types of .env files automatically that means there is no need for packages like dotenv to read .env files. Use bun run env to print all environment variables.
  • .env file variables can be accessed using process.env.VARIABLE_NAME or Bun.env.VARIABLE_NAME

BUN APIs

Bun also comes with its own set of native APIs implemented directly on the Bun object therefore, there is no need to use external packages to create a HTTP server, read/write files and even use the inbuilt sqlite database.

Bun.serve vs Node.js http module:

Bun.serve({
  fetch(req: Request) {
    return new Response("Bun!");
  },
  port: 3000,
});
require("http")
  .createServer((req, res) => res.end("Bun!"))
  .listen(8080);

According to Bun docs:

Bun.serve server can handle roughly 2.5x more requests per second than Node.js on Linux.

For File Handling Bun.file and Bun.write are the recommended ways of perform file related tasks. Though it is still a partial implementation of the node:fs module and still lacks some key functions.

const txt_file = Bun.file("foo.txt");  // read foo.txt

// Read json files

const json_file = Bun.file("notreal.json", { type: "application/json" }); //change application type to read different file types
notreal.type; // => "application/json;charset=utf-8"

// Copy content from foo.txt and save to a different file

const output = Bun.file("output.txt"); // doesn't exist yet!
await Bun.write(output, txt_file);

These functions for reading, writing files are highly optimized in fact according to Benchmarks using Bun to output file content using stdout works 2x faster than the Linux cat command!

Automatic Reloading

  • Bun provides two CLI commands for automatic reloading which means there is no need for third party modules like nodemon to refresh changes while development.
  • --watch mode hard restarts Bun's process when imported files change
  • --hot flag enables hot reloading where it does not restart every time a change is made instead it performs a soft reload where only new changes are updated and global state is persisted.
  • This is really important for HTTP connections and Websockets since using packages like nodemon or --watch mode will restart the entire process which results in loss of stateful objects while using --hot mode ensures that the code reloads with updated changes without interrupting the HTTP connections.

Wrapping up

Bun looks like a solid replacement for current Web applications built over current JavaScript frameworks, with its all-in-one functionality it is bound to take over the world of JavaScript and will re-define how modern developers will leverage its power & efficiency.

Though still in its early stages it is a bit rough around the edges, but owing to its great developer support, extensive documentation and constant updates it is improving swiftly and will soon become a staple tool in every developer's arsenal.