Rails 7 ships with a lot of really great improvements to the frontend the biggest of which is removing webpacker
in favor of a more generic approach to allow developers to use whatever they'd like to use to compile their assets like esbuild
or import maps or even webpacker
if that's still your thing. This works really great during development because there are some commands that get run to recompile your assets on the fly. Let's take a look at what I'm talking about.
Rails 7 comes with a couple of gems that better handle asset compilation. They are cssbundling-rails
and jsbundling-rails
. When installed, these add a new executable called bin/dev
which uses the foreman
gem to run multiple processes with one command. Those processes can be found in the Procfile.dev
and handle compilation of the CSS and JS assets as they change in real time while running in development.
RAILS_ENV=development
bin/dev
This script checks if you have foreman
installed and installs it if you don't. Then it starts running all the processes found in your Procfile.dev
.
#!/usr/bin/env bash
if ! foreman version &> /dev/null
then
echo "Installing foreman..."
gem install foreman
fi
foreman start -f Procfile.dev "$@"
Procfile.dev
Here's an example of the Procfile.dev
with processes for web
, js
, and css
. You'll notice the --watch
argument passed to the build commands. This "watches" your files and as you save changes they will be recompiled automatically. This is an important thing to note because your files while in development mode will always be recompiled into the latest version but we don't run foreman
or these commands in test by default.
If you're using a PaaS like Heroku you can also have a regular Procfile
that is used in production environments to outline what processes should be run there.
web: bin/rails server -p 3000
js: yarn build --watch
css: yarn build:css --watch
The Gotcha
The asset "application.js" is not present in the asset pipeline.
Great, now our assets are compiling as fast as we can change them and we are writing code like crazy. With all that new code we definitely need some tests to make sure things are working as expected. System tests are a great way to do that because it runs a full web server and a headless browser to click around pages, fill in forms, etc. But when we run our system tests we get this error:
ActionView::Template::Error:
The asset "application.js" is not present in the asset pipeline.
RSpec Support
Huh, well that's not good. This is because while our bin/dev
runs the asset compilation commands for our development environemtn our test environment does not. To fix this I added a support file to my RSpec setup that looks like this:
# spec/support/generate_assets.rb
# frozen_string_literal: true
RSpec.configure do |config|
config.before(:suite) do
system("yarn build", out: File::NULL)
system("yarn build:css", out: File::NULL)
end
end
Now before our tests are run we compile the assets using the same commands that are used during development. I added the out: File::NULL
to quiet the output of those asset compilations from the test logs. Now the application.js
is present and correct before every test run.
CI Check
This works great when running locally but I found that our CI was doubling up efforts because the CI builds everything on its own so these commands were not relevant there. Here is the full version of the file I use with a check if we are in a CI environment or not.
# spec/support/generate_assets.rb
# frozen_string_literal: true
unless ENV.fetch("CI", false)
RSpec.configure do |config|
config.before(:suite) do
system("yarn build", out: File::NULL)
system("yarn build:css", out: File::NULL)
end
end
end
Conclusion
That's it! Now our development environment constantly recompiles assets while we edit them thanks to the --watch
argument and our test assets are compiled before every run.
I don't really love this patch though. It seems to me that this should already be handled through the gems to better facilitate the Rails Magic™ and have things work out of the box. I understand that not everyone uses RSpec but I would think the gems could do a simple check like the bin/dev
file does to see which testing framework is in use and trigger compilation accordingly.
I hope this was helpful and feel free to leave comments if you know of a better way to do things or if I've missed anything.