Assets in Modern Rails

Rails 7 has “three great answers to JavaScript,” but the one answer I want to focus on is the set of bundling gems that are available: jsbundling-rails and cssbundling-rails. These are very slim gems that provide a very conventional way to bundle assets for a modern Rails application by delegating it all to scripts in your package.json file.

This is the gist of the cssbundling gem. It’s just a rake task that is added to the assets:precompile task.

namespace :css do
  task :build do
    system "yarn install && yarn build:css"
  end
end

Rake::Task["assets:precompile"].enhance(["css:build"])

And it expects yarn build:css to put your compiled stylesheet in /app/assets/builds/application.css, and if you’re using Sprockets, the builds directory needs to be added to your assets manifest.js file.

That’s it.

All the heavy lifting is done by what you’ve configured build:css to do. And the jsbundling-rails gem is nearly identical, except it calls build in your package.json file.

Delegating the asset compilation to front-end tooling means you can follow the setup guides for Tailwind CSS or ESBuild without much thought as to how either should work with Rails.

The “magic” happens with Sprockets knowing about these files through the manifest configuration file.

In Development Mode

There are some drawbacks to this setup. Changes you make to your source files are not picked up automatically in development in your rails server process like the traditional Rails assets pipeline. You will need an external process watching your source files for changes and putting the compiled changes in the builds directory.

Both bundling gems setup a Procfile.dev and a bin/dev executable that will delegate running rails server and your building script(s) in watch mode concurrently to the foreman gem.

In Practice

At my work, we don’t use yarn, so we’ve actually recreated these gems as a simple rake file in our project that calls npm instead. Normally I would be against recreating the functionality of a gem and taking on the maintenance burden, but these gems do so little in practice.

These gems are a breath of fresh air compared to the now retired Webpacker gem. They stay true to the convention over configuration mantra of Ruby on Rails and will not add a brittle component to your asset pipeline that will cause you worry as Rails or your preferred asset libraries get updated.