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
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.
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
if ! foreman version &> /dev/null then echo "Installing foreman..." gem install foreman fi foreman start -f Procfile.dev "$@"
Here's an example of the
Procfile.dev with processes for
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 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.
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.
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
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.