Since Rails (6) is now shipped with Webpacker, this latter tends to be the new standard for the assets precompilation. RIP Sprockets.
Like me, you might not be an expert of ES6 and therefore be reluctant to jump into it. Like me, you might also have tried to create a new project and keep using Sprockets :
$ rails new MissYouSprockets --skip-javascript --no-skip-sprockets
Result: Rails forces you into the new world: your assets compilation is not fully configured to use Webpacker nor Sprockets. This article does not intend to help you keep up with the old ways so I won’t drag on this matter.
This article will show you step by step how to fully configure Bootstrap 4, JQuery and bonus : the hot module reloading ❤.
Note: As usual, you’ll find the source code here with a branch for each section of this article.
Building the project foundation
At the time of this article, I used the latest Rails version (6.0.3.1) and Ruby 2.6.7. When creating the project, I just got rid of Turbolinks because i’m not familiar with it (it’s up to you).
$ rails -v
=> Rails 6.0.3.1
# Just to make sure that the Rails version is 6.0.3.1$ rails new WebpackHowTo --skip-turbolinks
Now we’ll also add a controller and an action in order to be able to test what we’ll be doing.
$ cd WebpackHowTo
$ rails g controller Welcomes index
=> ... [OK]
Provide Plugins: JQuery and Popper.js
If we take a look at the Bootstrap 4 official page, we can see that the latest version at this time is 4.5.0 and that it will require JQuery and Popper.js. Let’s add them via Yarn :
$ yarn add jquery popper.js => yarn add v1.22.4
=> [1/4] 🔍 Resolving packages...
=> [2/4] 🚚 Fetching packages...
=> [3/4] 🔗 Linking dependencies...
=> [4/4] 🔨 Building fresh packages...
=> success Saved lockfile.
=> success Saved 3 new dependencies.
=> info Direct dependencies
=> ├─ bootstrap@4.5.0
=> ├─ jquery@3.5.1
=> └─ popper.js@1.16.0
=> info All dependencies
=> ├─ bootstrap@4.5.0
=> ├─ jquery@3.5.1
=> └─ popper.js@1.16.0
=> ✨ Done in 3.64s.
JQuery and Popper.js will be used across all the app so we can qualify them as plugin. We don’t want to import them in each different pack so there is a special method for this with Webpacker : ProvidePlugin. This can be done in config/webpack/environment.js as it will be relevant independently of the current environment :
# in config/webpack/environment.jsconst { environment } = require(‘@rails/webpacker’);const webpack = require(‘webpack’);environment.plugins.append(‘Provide’, new webpack.ProvidePlugin({
$: ‘jquery’,
jQuery: ‘jquery’,
Popper: [‘popper.js’, ‘default’]
}));const config = environment.toWebpackConfig();config.resolve.alias = {
jquery: ‘jquery/src/jquery’
};module.exports = environment;
Note that the previous config also makes the plugins globally available. In other words: JQuery is now accessible from the console (🙌🏽) :
JQuery globally available (makes your debugging easier).
Organizing your assets
Photo by Edgar Chaparro on Unsplash
Now it’s time to organize our app/javascript folder to keep the different assets separated.
Note that this project frontend is not a javascript app such as Vue, Angular or ReactJs. The following organization is fitted for a regular project with Sass, JQuery and Images.
Here is how I like to organize my files:
app/javascript/
- channels/
// [default]
- images/
// All your images here
- js/
- site.js // equivalent to old "application.js"
- packs/
- application.js // your first "pack"
- scss/
- site.scss // equivalent to old "application.scss"
Of course, inside the images, js and scss folders you should organize your files properly to keep it scalable.
Now let’s configure our first pack.
# in app/javascript/packs/application.jsrequire("@rails/ujs").start()
require("@rails/activestorage").start()
require("channels")// CSS
import 'scss/site'// JS
import('js/site')// Images
const images = require.context('../images', true)
const imagePath = (name) => images(name, true)
We did 3 main changes on the file :
Importing the “scss/site.scss” file
It will be the equivalent to your application.scss file.
Importing the “js/site.js” file
It will be the equivalent to your application.js file.
Uncommenting the code needed for the image_pack_tag helper.
Note that now, you’ll need to manually import any .js or .scss/.css file that you’ll need in the corresponding folder. For instance, if you create a variables/_colors.scss file, then you’ll need to import it in your site.scss file :
// in app/javascript/scss/site.scss @import "variables/colors";
The logic is now pretty much the same than with Sprockets.
Last but not least. If you check in your views layout file. You’ll see :
<%= stylesheet_link_tag ‘application’, media: ‘all’ %>
<%= javascript_pack_tag ‘application’ %>
There is a pack for the javascript and a stylesheet for the scss.
What !? But why all the assets are required in a single file in app/packs/application.js then ?
If you check in your config/webpacker.yml file, you’ll see that extract_css is false for the development environment but true for the production env. If you go on and try to delete the stylesheet_link_tag helper, and restart your server, you’ll see that your scss assets are still loaded normally. On the other hand, if you change the extract_css to false and restart your server then you’ll have no more styling. How to fix it ?
<!DOCTYPE html>
<html>
<head> <%= stylesheet_pack_tag ‘application’, media: ‘all’ %>
<%= javascript_pack_tag ‘application’ %>
</head>[...]
Just change the stylesheet_link_tag helper to a stylesheet_pack_tag helper. Then you’ll see that your style is back. When extract_css is true, Rails extracts all the styling files (.css, .scss, etc.) in a separate file thanks to the mini-css-extract plugin.
So what the point of having a different configuration between environment ? Hold on Bobby. That’s the next section.
Webpacker hot-reloading module
Photo of “hot peppers” by Prince Abid on Unsplash
Rails 6 and Webpacker now provide an out-of-the-box hot reloading module. It means that any time you persist a change in a javascript of stylesheet file of your app, the page will reload in your browser.
Awesome no ? This will save you a lot of cmd + R. 🎉
How to set this up ? Easy, just go to your terminal, open a new tab in your project directory and run the following command :
$ bin/webpack-dev-serverThe results of the command bin/webpack-dev-server
Now, if you head up to your site.scss file for instance and if you do any change, you’ll see that your browser reloads automatically. ✔
Want more ? This article from Alessandro Rodi explains you how to set up HMR (Hot Module Reloading). t’s important to avoid full page reloading when working on a very big project. When configured right, you can recompile only touched stylesheets, which will fasten page reloading a great deal
Adding Bootstrap
Now back on tracks. Let’s add Bootstrap via Yarn:
$ yarn add bootstrap=> yarn add v1.22.4
=> [1/4] 🔍 Resolving packages…
=> [2/4] 🚚 Fetching packages…
=> [3/4] 🔗 Linking dependencies…
=> [4/4] 🔨 Building fresh packages…
=> success Saved lockfile.
=> success Saved 1 new dependency.
=> info Direct dependencies
=> └─ bootstrap@4.5.0
=> info All dependencies
=> └─ bootstrap@4.5.0
=> ✨ Done in 4.03s.
Now we need to require both its js and scss files. Head up to our folder app/javascript/.
First the js file :
// in app/javascript/js/import 'bootstrap'
… and the scss file :
// in app/javascript/scss/@import “~bootstrap/scss/bootstrap.scss”;
At this point, your HMR should have reloaded your page two times (one for each file). The second time, you should have noticed that the default font of your app have changed to System-ui (the default from Bootstrap 4). That means Bootstrap is ready to go.
If you don’t like the javascript folder …
Webpacker defaults the source_path location of the assets to app/javascript. Maybe I’m being old school but seing a javascript (with no “s”) folder makes me grumpy.
I like to rename it webpacker :
# in config/webpacker.ymldefault: &default
source_path: app/webpacker # instead of "app/javascript"
source_entry_path: packs
public_root_path: public
public_output_path: packs
cache_path: tmp/cache/webpacker
Don’t forget to restart your webpack-dev-server after.
Et voilà ! 👌
PS : Thanks to Kernael who helped me for the provide plugins part.