Jeremy Smith on October 19, 2021

Migrating from Webpacker to the New Bundling Approach

You may have heard that Rails 7 will have three great answers to Javascript in 2021. Well, you don’t have to wait until Rails 7 is released to switch from webpacker to the new, simpler bundling approach, using jsbundling-rails and cssbundling-rails.

If you’re on Webpacker today, it’s a very modest jump to switch to one of the bundlers made available through the jsbundling-rails gem. You don’t even have to stick with Webpack. Fundamentally, they all work in the same way: take an entry point, produce a build.

This isn’t a step-by-step guide, and probably won’t age well (as things are still in flux with the new bundling gems), but having migrated a couple Rails apps from webpacker to the new bundling approach, I have some tips that will save you time and headaches.

1. Use Foreman for process management

If you’re not using it already, use the foreman gem to manage your various processes. These days, it’s common and expected to have multiple processes running in development. You are rarely just going to run rails s, as you’ll probably need a background worker process, and potentially others. If you are coming from webpacker, you’ll be dropping the bin/webpack-dev-server process for these new build processes.

Set up a Procfile.dev for all your development processes like this:

web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
js: yarn build --watch
css: yarn build:css --watch

Note: the foreman docs recommend you not add it to your Gemfile.

2. Use multiple entrypoints when spanning old and new approaches

If you were the middle of a migration to webpacker from the classic Sprockets approach (like I was), and still have JS under app/assets/javascripts, you could just setup separate entrypoints. Just make sure they have different names, so there isn’t a collision:

In your application.html.erb:

<%= javascript_include_tag "legacy", "application" %>

In your manifest.js:

//= link_tree ../builds
//= link_tree ../images
//= link legacy.js

3. Let Sprockets handle compression

You probably still want to do asset compression through Sprockets, with uglifier for JS and sassc-rails for CSS.

Note: for uglifier to work with ES6, you need to enable harmony mode. (See the README.)

config.assets.js_compressor = Uglifier.new(harmony: true)

Even if your app doesn’t use SASS (e.g. if you are using Tailwind) sassc-rails is still the most common gem used for CSS compression. The Edge Rails Guides say:

The sassc-rails gem is automatically used for CSS compression if included in the Gemfile and no config.assets.css_compressor option is set.

4. Copy CSS assets and update asset paths

If you include CSS packages with their own assets (like FontAwesome with font files), you can use postcss as the build tool, and use the postcss-copy plugin, configured like so:

require('postcss-copy')({
    dest: 'app/assets/builds'
}),

This will ensure that assets are copied to the builds folder. However, this doesn’t update asset paths for digested assets in production. See this discussion and this (currently-open) PR.

Until cssbundling-rails is updated to enable this, you can add a Sprockets postprocessor class, like those suggested in the discussion and PR, that will update asset paths to use the digested versions.

5. Make sure build scripts run before test suite

If you are using Minitest, the bundling gems will automatically enhance the test:prepare task so that your build scripts run before your tests. But if you are using RSpec, you’ll need to do this yourself.

You’ll want to add the Rake::Task enhance calls found in the build Rake files for cssbundling-rails and jsbundling-rails to your Rakefile, something like this:

if Rake::Task.task_defined?("spec:prepare")
  Rake::Task["spec:prepare"].enhance(["css:build", "javascript:build"])
end

Also, make sure you run rails spec rather than rspec so that it picks up the enhancements.

6. Use esbuild-rails for JS file globbing

If you are dropping webpacker for esbuild, that means you’ll lose the file globbing used, for example, when loading all the Stimulus controllers in a directory. The best approach right now appears to be using Chris Oliver’s plugin, esbuild-rails. See his GoRails video for details.

Update 2021-10-20: bonus tip from Breno Gazzola

7. Handling multiple CSS entrypoints

Just like with Javascript, you can also have multiple CSS entrypoints. To do this with the sass build option, for example, place all your entrypoints in one folder, the sources in another, and adjust the packages.json file:

build:css": "sass ./app/assets/stylesheets/entrypoints:./app/assets/builds --no-source-map --load-path=node_modules"

Need help building or maintaining a Rails app?

Jeremy is currently booked until mid-2023, but always happy to chat.

Email Jeremy