How to use Bun with Ruby on Rails
Tags: rails, ruby, bunjs, tutorial
Published: 2024-03-05 19:26:02

---
title: How to use Bun with Ruby on Rails
published: true
date: 2024-01-07 19:18:42 UTC
tags: rails, ruby, bun, tutorial
canonical_url: https://web-crunch.com/posts/bun-with-ruby-on-rails
---

{% youtube XzXKZduCvII %}

In the Ruby on Rails world, we often leverage tools like [node.js](https://nodejs.org/en) and [yarn](https://yarnpkg.com/) as a build step for front-end tooling like Tailwind CSS, Stimulus.js, and many other front-end technologies.

As [more hype](https://twitter.com/dhh/status/1740119521443340740) grows about removing the “build” from the equation, I, for one, don’t necessarily see the additional dependencies and tooling as a bad thing. It's made so much of what I do way more efficient.

A new approach called [Bun](https://bun.sh/) has entered the scene and offers those same benefits and more. Most promising is the massive leap in build speed. I heard about this addition to Rails and have meant to dive in to try it out for some time. This blog post and video is an answer to that desire.

## What is Bun?

Bun is an all-in-one toolkit for JavaScript and TypeScript apps. It ships as a single executable called `bun​`. You can compare it to npm and yarn.

On the [marketing site](https://bun.sh/) there's a section that reads:

> Replace `yarn` with `bun install` to get 30x faster package installs.

So you can see why I might be intrigued. To be 100% fair, I don’t leverage a lot of JavaScript. Ruby on Rails is my go-to for more features. In the event I need more interactivity, I reach out to Stimulus.js. That being said, if I stand to benefit from quicker installs of packages and build times, I will gravitate toward that route.

Here are the design goals as quoted from Bun’s website

> Bun is designed from the ground up with today's JavaScript ecosystem in mind.

- **Speed**. Bun processes start [4x faster than Node.js](https://twitter.com/jarredsumner/status/1499225725492076544) currently (try it yourself!)
- **TypeScript & JSX support**. You can directly execute `.jsx`, `.ts`, and `.tsx` files; Bun's transpiler converts these to vanilla JavaScript before execution.
- **ESM & CommonJS compatibility**. The world is moving towards ES modules (ESM), but millions of packages on npm still require CommonJS. Bun recommends ES modules, but supports CommonJS.
- **Web-standard APIs**. Bun implements standard Web APIs like `fetch`, `WebSocket`, and `ReadableStream`. Bun is powered by the JavaScriptCore engine, which is developed by Apple for Safari, so some APIs like [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) and [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) directly use [Safari's implementation](https://github.com/oven-sh/bun/blob/HEAD/src/bun.js/bindings/webcore/JSFetchHeaders.cpp).
- **Node.js compatibility**. In addition to supporting Node-style module resolution, Bun aims for full compatibility with built-in Node.js globals (`process`, `Buffer`) and modules (`path`, `fs`, `http`, etc.) _This is an ongoing effort that is not complete._ Refer to the [compatibility page](https://bun.sh/docs/runtime/nodejs-apis) for the current status.

> Bun is more than a runtime. The long-term goal is to be a cohesive, infrastructural toolkit for building apps with JavaScript/TypeScript, including a package manager, transpiler, bundler, script runner, test runner, and more.

## Bun with Rails

In [a recent pull request to the jsbundling-rails gem](https://github.com/rails/jsbundling-rails/pull/167), support for Bun was added to Rails as a JavaScript runtime.

You can install it like other options, including `build,` `rollup,` and `webpack.` I usually reach for `esbuild` but wanted to try `bun`

### Create a demo Rails app using bun

Let’s create a simple app and give `bun` a shot

```bash
rails new bun_demo -c tailwind -j bun
```

This installs a vanilla Rails application with Tailwind CSS and Bun for the front end.

### What about existing apps?

Do you have an existing app and want to use Bun? Assuming you’ve installed the `jsbundling-rails` gem, you can run the following task:

```bash
./bin/rails javascript:install:bun
```

### Preview the `bun.config.js` file

When you run the new rails app command above in the latest version of Rails (7.1), a new `bun.config.js` file is created in the project's root. Inside is the following code.

```javascript
import path from 'path';
import fs from 'fs';

const config = {
  sourcemap: "external",
  entrypoints: ["app/javascript/application.js"],
  outdir: path.join(process.cwd(), "app/assets/builds"),
};

const build = async (config) => {
  const result = await Bun.build(config);

  if (!result.success) {
    if (process.argv.includes('--watch')) {
      console.error("Build failed");
      for (const message of result.logs) {
        console.error(message);
      }
      return;
    } else {
      throw new AggregateError(result.logs, "Build failed");
    }
  }
};

(async () => {
  await build(config);

  if (process.argv.includes('--watch')) {
    fs.watch(path.join(process.cwd(), "app/javascript"), { recursive: true }, (eventType, filename) => {
      console.log(`File changed: ${filename}. Rebuilding...`);
      build(config);
    });
  } else {
    process.exit(0);
  }
})();
```

Here, we have a general configuration for `bun`, which declares the entry point of the app and the output points.

The configuration also watches for changes within the `app/javascript` directory, so you needn't refresh the page as you code. This was a massive milestone in front-end web development, and now we all seem to take it for granted :).

Notice Stimulus.js is installed by default and preconfigured to “Just Work". Remember the motto “Convention Over Configuration”? that’s at play here, and I love this stuff!

Much like `esbuild`, a file builds to the `app/assets/builds` directory in real-time. In this case, it would be called `application.js`.

This configuration file gets processed with a script inside the `package.json` file. There I have the following scripts:

```json
// package.json

"scripts": {
    "build": "bun bun.config.js",
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify"
  },
```

Because I installed Tailwind CSS and Bun, we see both scripts present.

Finally, in the current version of Rails (7.1) there’s a `Procfile.dev` file that gets sourced to a ruby gem called [foreman](https://github.com/ddollar/foreman).

That file bundles all the scripts we need to run into one set of processes giving us the ability to run one command to boot the rails app and build the assets.

```bash
bin/dev
```

Lovely, although a spiderweb of stuff, right? Luckily, it works out of the box, and you don’t have to pre-configure anything to install.

## Not so fast!

If you’re following along and you ran `bin/dev` in the previous step, you might see an error like this in your logs:

```bash
unknown command: bun run build --watch
```

Bun still needs to be installed on your system if you see this. There are several ways to go about installing it. I will reach for [homebrew](https://brew.sh/), given I’m on a Mac. Read [the docs](https://bun.sh/docs/installation) for a different setup.

```bash
brew tap oven-sh/bun 
brew install bun
```

You can check if it’s installed by running

```bash
bun --version
```

## Usage

Bun is super similar to yarn in syntax and key commands. You can use it like the following:

```bash
bun add -d tailwindcss
bun add @hotwired/turbo-rails @hotwired/stimulus
```

To run `bin/dev` we need to install dependencies using `bun`.

```bash
bun install
# bun install v1.0.20 (09d51486)
# 12 packages installed [1.66s]
```

This happens quickly, but while Rails created the Stimulus and Hotwire files inside `app/javascript`, the dependencies we need aren't installed.

```bash
bun add @hotwired/stimulus @hotwired/turbo-rails
```

Now running `bin/dev`, we should be set up for success!

## What now?

Well, build cool stuff like normal! With bun under the hood for JavaScript, you get the added benefits of speed and compatibility without changing too much “muscle memory”.

Personally, I have used yarn and [esbuild](https://esbuild.github.io/) exclusively since[jsbundiling-rails](https://github.com/rails/jsbundling-rails) and [cssbundling-rails](https://github.com/rails/cssbundling-rails) entered the scene. I’m excited to give Bun a run for its money and will likely switch to it.