esbuild is very useful in new Rails apps, even with the very last Rails 8 version. Let's see how to customise it.

· Rails  · 4 min read

Custom esbuild for Rails

esbuild is very useful in new Rails apps, even with the very last Rails 8 version. Let's see how to customise it.

0. Motivation

Rails 8 decided to move to Propshaft and importmaps, however they let the plain old esbuild door opened, fortunately.

Here is a trick to allow more esbuild configuration.

1. Prerequisites

$> ruby -v
ruby 3.0.0p0 // you need at least version 3 here
$> bundle -v
Bundler version 2.2.11
$> npm -v
8.3.0 // you need at least version 7.1 here
$> yarn -v
1.22.10

Any upper versions should work.

Build a default Rails 7 application

mkdir customesbuild && cd customesbuild
echo "source 'https://rubygems.org'" > Gemfile
echo "gem 'rails', '7.0.0'" >> Gemfile
bundle install
bundle exec rails new . --force --css=bootstrap

# Create a default controller
echo "class WelcomeController < ApplicationController" > app/controllers/welcome_controller.rb
echo "end" >> app/controllers/welcome_controller.rb

# Create a default route
echo "Rails.application.routes.draw do" > config/routes.rb
echo '  get "welcome/index"' >> config/routes.rb
echo '  root to: "welcome#index"' >> config/routes.rb
echo 'end' >> config/routes.rb

# Create a default view
mkdir app/views/welcome
echo '<h1>This is h1 title</h1>' > app/views/welcome/index.html.erb

# Create database and schema.rb
bin/rails db:create
bin/rails db:migrate

Side note 1 For Rails 7.0.0, if you don’t specify any css framework, Rails will not give you any jsbundling by default. You will have to rely on Sprockets (for CSS) and importmaps (for JS)

Side note 2 If you want to create new Rails application more easily, we already wrote an article about this here : https://bootrails.com/blog/how-to-create-tons-rails-applications/

Check everything is working properly

Modify application.js as follow

// inside app/javascript/application.js
import '@hotwired/turbo-rails';
import './controllers';
import * as bootstrap from 'bootstrap';

console.log('I am the default application.js');

And go to your terminal, and enter

./bin/dev

Now open your browser, the title displayed should be big enough, meaning CSS is loaded correctly (we relied on Bootstrap here), and if you open the Developer’s Tool, in the “Console” Tab, you should be able to see “I am the default application.js”. That means our JavaScript is also properly configured.

Modify package.json

Default package.json should look like this

{
  "name": "app",
  "private": "true",
  "dependencies": {
    "@hotwired/stimulus": "^3.0.1",
    "@hotwired/turbo-rails": "^7.1.0",
    "@popperjs/core": "^2.11.2",
    "bootstrap": "^5.1.3",
    "esbuild": "^0.14.11",
    "sass": "^1.48.0"
  },
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds",
    "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules"
  }
}

Now replace the property named “build” like this :

{
  "name": "app",
  "private": "true",
  "dependencies": {
    "@hotwired/stimulus": "^3.0.1",
    "@hotwired/turbo-rails": "^7.1.0",
    "@popperjs/core": "^2.11.2",
    "bootstrap": "^5.1.3",
    "esbuild": "^0.14.11",
    "sass": "^1.48.0"
  },
  "scripts": {
    "build": "node esbuild.config.js",
    "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules"
  }
}

Great ! We don’t have any file named esbuild.config.js, so let’s build it at the root of our project.

Create esbuild.config.js

Now create esbuild.config.js at the root of your project, as follow :

const path = require('path');

require('esbuild')
  .build({
    entryPoints: ['application.js'],
    bundle: true,
    outdir: path.join(process.cwd(), 'app/assets/builds'),
    absWorkingDir: path.join(process.cwd(), 'app/javascript'),
    watch: true,
    // custom plugins will be inserted is this array
    plugins: [],
  })
  .catch(() => process.exit(1));

As you may have noticed, the “plugins” property allows you to add any esbuild plugin available in the npm ecosystem. All other properties can be added/modified as wanted of course. But at least you have the bare minimum that doesn’t hurt the default Rails frontend assets management.

Restart your local development server

Stop the local server. Restart it : in your terminal, type

./bin/dev

You should see node esbuild.config.js --watch somewhere in the middle of the logs :)

./bin/dev
14:25:45 web.1  | started with pid 69446
14:25:45 js.1   | started with pid 69447
14:25:45 css.1  | started with pid 69448
14:25:45 js.1   | yarn run v1.22.10
14:25:45 css.1  | yarn run v1.22.10
14:25:45 css.1  | $ sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules --watch
14:25:45 js.1   | $ node esbuild.config.js --watch
14:25:46 web.1  | => Booting Puma
14:25:46 web.1  | => Rails 7.0.0 application starting in development
14:25:46 web.1  | => Run `bin/rails server --help` for more startup options
14:25:46 css.1  | Sass is watching for changes. Press Ctrl-C to stop.

That means our own esbuild config was properly read by Rails.

Check in your browser that CSS and JS work properly, as above.

Done !

Share:
Back to Blog

Related Posts

View All Posts »
Rails and Sidekiq tutorial

Rails and Sidekiq tutorial

The need for the existence of background jobs arises very quickly when you create a new Rails application for business purposes. Sidekiq was, for a long time, the de-facto tool of the Rails stack.

Rails and Cypress testing

Rails and Cypress testing

Cypress is very interesting, since it matches one of the core Rails philosophy, which is "value integrated system"