
Do you feel lost with all the changes related to assets and Javascript? Npm, Babel, ES6, Yarn, Webpack, Webpack, Webpacker, Sprockets, does all of this seem completely foreign to you?
If you need a quick and easy to understand overview of how this entire Javascript ecosystem works in a Rails 6 application, this article is what you are looking for.
I'll end this article with a step-by-step section on how to add Bootstrap 4 and FontAwesome 5 to a Rails 6 project.
NPM is a Javascript package manager (NodeJS modules to be precise). It's the Rubygems of the Javascript world.
npm install <package>
For example, if you want to install Bootstrap:
npm install bootstrap
NPM stores downloaded packages in . /node_modules and keep a list of these packages in . /package.json.
At this point I am not making any connection between NPM and Rails, keep reading to understand why.
Yarn is a newer package manager for Javascript. It fetches packages from the NPM repository but does more than that. It allows you to lock the desired versions of your NPM packages in an auto-generated yarn.lock file (similar to Gemfile.lock), it is much faster than NPM, etc.
In a Rails 6 application, when you need a Javascript library, you:
app/assets/application.js (which was compiled by Sprockets)
Yarn Add <package>, so you demand it (we'll see how later).
Note: Since then, NPM has also added a locking function via package-lock.json
ES6 is a new Javascript standard (a new version of Javascript if you want). It comes with new and very practical features such as class definition, destructuring, arrow functions, etc.
Goodbye Coffeescript, I always hated you.
Since not all web browsers understand ES6 yet, you need a tool that reads your ES6 Javascript code and translates it into old ES5 Javascript so that it works on all browsers. Babel is the compiler that does this translation.
There's Babel and there's Yarn and their configuration files and there's the need to automate the compilation of your assets and the management of environments and so on.
Since you want to focus on writing code and automating the precompilation of resources, you will use Webpack who plays the role of conductor. It takes your resources and forwards each of them to the appropriate plugins. The plugins then ensure that the right tool processes the input file and delivers the expected result.
For example, Webpack can:
Babel-loader for Babel to compile ES6 Javascript code into ES5,<script type="text/javascript" src="path-to-es5-javascript-pack.js"></script>).Webpacker is a gem that makes it easy to include Webpack in your Rails application. It comes with a few initial configuration files (and enough to get started) so you can start writing real code without worrying about configuration.
The default Webpacker configuration is as follows:
app/javascript/packs/ must contain your Javascript packs (for example: application.js)
<pack_name>javascript_pack_tag ''(for example: <%= javascript_pack_tag 'my_app' %> Will understand app/javascript/packs/my_app.js)I'll give a very clear example of how all of this works at the end of this article, but first I need to talk a bit about Sprockets.
Note: another default configuration is extract_css: false (config/webpacker.yml), which means that, although Webpack knows how to serve CSS packages with stylesheet_pack_tag, you ask him not to do it. This article is all about Javascript, so I'm not going to say more about it, but keep in mind that this option is disabled by default, so you don't have to waste time debugging what's a default behavior, not a bug.
Yet another note: when you run rails assets:precompile, you might think that Rails will only pre-compile what's in the file app/assets/. In fact, Rails will pre-compile both Webpack assets app/javascript/ And those of Sprockets app/assets/.
Like Webpack, Sprockets is an asset pipeline, which means it takes asset files as input (Javascript, CSS, images, etc.) and processes them to produce output in the desired format.
Starting with Rails 6, Webpack (er) replaces Sprockets as a new standard for writing Javascript in your Rails applications. However, Sprockets is still the default way to add CSS to your applications.
With Sprockets you:
config.assets.precompile (Sprockets 3, Rails 5)app/assets/config/manifest.js (Sprockets 4, Rails 6)If you want to include an asset from the Sprockets pipeline, you'll need to:
app/assets/stylesheets/my_makeup.css)app/assets/config/manifest.js make it available for stylesheet_link_tag or through a Link_Tree, Link_directory Or a Link statement (for example: Link my_makeup.css)stylesheet_link_tag (<%= stylesheet_link_tag 'my_makeup' %>)Understanding the following is essential if you don't want to waste countless hours rowing against the tide. Ideally, you should spend some time learning ES6, but in the meantime, I can at least say this:
Webpack is different from Sprockets in that it compiles modules.
ES6 modules to be precise (in the case of Rails 6 with a default configuration). What does that mean? Well, that implies that everything you declare in a module is in a way namespaces because it's not meant to be accessible from the global scope, but rather imported and then used. Let me give you an example.
You can do the following with Sprockets:
app/assets/javascripts/hello.js:
function hello (name) {console.log (“Hello" + name + “!”) ;}
app/assets/javascripts/user_greeting.js:
function greet_user (last_name, first_name) {hello (last_name + "" + first_name);}
app/views/my_controller/index.html.erb:
<button onclick="greet_user('Dire', 'Straits')"><%= javascript_link_tag 'hello' %><%= javascript_link_tag 'user_greeting' %> Hey!</button>
It's pretty easy to understand. And now with Webpacker?
If you were thinking of just moving these JS files under app/javascript/packs, include them using javascript_pack_tag and be done, let me stop you there: that wouldn't work.
Why? Because hello () would be compiled as being in an ES6 module (same for the module user_greeting ()), which means that insofar as user_greeting () goes, even after both JS files are included in the view, the function hello () the function does not exist.
So how do you get the same result with Webpack:app/javascript/packs/hello.js:
export function hello (name) {console.log (“Hello" + name + “!”) ;}
app/javascript/packs/user_greeting.js:
import {hello} from '. /hello '; function greet_user (last_name, first_name) {hello (last_name + "" + first_name);}
app/views/my_controller/index.html.erb:
<button onclick="greet_user('Dire', 'Straits')"><%= javascript_pack_tag 'user_greeting' %> Hey!</button>
Would that work? No. Why? Again, for the same reason: Greet_User is not accessible from view because it is stashed inside a module once it's compiled.
We finally get to the most important point in this section:
So how do you get a button to trigger a JS action? From a pack, you add behavior to an HTML element. You can do this using vanilla JS, jQuery, stimulusJS, etc. Here is an example using jQuery:
import $ from 'jquery';
import {hello} from '. /hello '; function greet_user (last_name, first_name) {
hello (last_name + "" + first_name);
} $ (document) .ready (function () {
$ ('button #greet -user-button') .on (
'click',
function () {
greet_user ('Say', 'Strait');
}
);
}); /* Or the ES6 version for this: */
$ () =>
$ ('button #greet -user-button') .on ('click', () => greet_user ('Say', 'Strait'))
);
app/views/my_controller/index.html.erb:
<button id="greet-user-button"><%= javascript_pack_tag 'user_greeting' %> Hey!</button>
As a general rule: With Webpack, you're setting up the behavior you want in packs, not in views.
Let me repeat myself with a final example:
If you need to use a library (select2 or jQuery for example), is it possible to import it into a pack and use it in a view? No You either import it into a pack and use it in that pack, or you're reading the next section of this article.
If you want to learn how to use StimulusJS to structure your JS code and attach behaviors to your HTML elements, I recommend that you read StimulusJS on Rails 101.
For those who want to understand how this “everything is hidden/namespaces” works: when an ES6 module is compiled into ES5 code, the module content is packed into an anonymous function so that outside of that function, you cannot access any variable/function declared in the module.
The Webpacker documentation states the following:
[...] the primary purpose of webpack is application-style JavaScript, not images, CSS, or even JavaScript Sprinkles (all of this continues to live in app/assets).
This means that if you need or want to make certain Javascript elements available for views, you can always use Sprockets.
app/assets/javascripts directory (note that the javascripts are in the plural here)app/assets/config/manifest.js as a result (//= link_directory.. /javascripts.js )javascript_include_tag (notice the difference:javascript_include_tag javascript_pack_tag for Webpacker)I personally try to avoid this as much as possible, but it's worth knowing.Note: you might ask why there is both a manifest.js file and a config.assets.precompile which serve the same purpose of exposing high-level targets to compilation. This is for backwards compatibility reasons. Les upgrade instructions advise you not to use the latter.
To help you better understand, I advise you to apply as you read. This will help you a lot to understand these new features.
Rails New Bloggy
I would like you to take a look at the following files. The aim is not for you to understand everything in them, but simply for you to know that they exist and to have a vague mental picture of what they contain so that you can easily come back to them later if necessary.
Yarn:
Webpacker:
Sprockets:
Rails Generate Controller Welcome Index
And add a root to : 'welcome #index ' In config/routes.rb.
Execute Rails Server and make sure everything is good so far.
We want to add bootstrap 4 (which requires jquery and popper.js) and font-awesome 5.
Take a quick look at Yar search enginen and try to find the necessary packages by yourself (note the number of downloads for each package), then continue with this tutorial.
yarn add bootstrap jquery popper.js @fortawesome /fontawesome-free
Yarn has now cached them in . /bloggy/node_modules/ and updated package.json. However, these packages are still not used in our application. Let's fix that. We're going to start by including the JS part and we'll take care of the CSS part later.
In your layout, there is already javascript_pack_tag 'application' which means you're asking Webpack to compile app/javascript/packs/application.js and include the output in this layout. To add bootstrap, we can either create another pack that's exclusively meant to include bootstrap, or use the pack application.js. Let's do the second solution since we're not building a real application.
Add the following text to app/javascript/packs/application.js:
require (“bootstrap”); require (” @fortawesome /fontawesome-free “);
Note: I required “bootstrap”, not “bootstrap/dist/js/bootstrap.min”. Indeed, unless I specify the path of a file, the package.json of the module (bloggy/node_modules/bootstrap/package.json) will provide the necessary information about the file to be included. I could have required “bootstrap/dist/js/bootstrap.min” and that would have worked just fine.
Let's go back to setting up bootstrap and fontawesome in our application. If you start up your Rails server and look at the Javascript console, you'll see that it's working properly even though we didn't require jQuery in application.js.
If you've already looked at other tutorials that explain how to include bootstrap via Webpacker, you've probably noticed that most of them require jQuery first and then bootstrap. This is actually useless.
Why? Because since we installed jQuery using Yarn, bootstrap alone can require jQuery. There is no reason for us to require it in application.js because that would make it available in application.js, not in the bootstrap module. So unless you need to use jQuery directly in the application application.js, there is no need to require it there.
I like working with SCSS, so before including bootstrap and font-awesome, let's rename application.css in application.scss and empty it of all comments and other instructions from Sprockets.
Now add the following code to it:
$fa-font-path: '@fortawesome /fontawesome-free/webfonts';
@import '@fortawesome /fontawesome-free/scss/fontawesome';
@import '@fortawesome /fontawesome-free/scss/regular';
@import '@fortawesome /fontawesome-free/scss/solid';
@import '@fortawesome /fontawesome-free/scss/brands';
@import 'bootstrap/scss/bootstrap';
Note: Unlike Webpack, Sprockets does not read files package.json npm modules to determine which file to include, and so you can't import a module just by name. You must specify the path to the file (s) you want to import (file extension is optional).
You are ready!
Let's add a button and an icon in our view to make sure everything is working properly:
Add <a href="#" class="btn btn-primary">Yeah<i class="far fa-thumbs-up"></i> To
</a>app/views/Welcome/index.html.erb, run your rails server and make sure the primary button and icon appear correctly.
If you need to use jQuery (or any dependencies) in most of your packs, requiring it in each pack is cumbersome. One solution I like is to make it available for all packs via configuration (again, it won't be available in views, only in packs).
To do this, copy/paste the following into the file config/webpack/environment.js:
const {environment} = require ('@rails /webpacker')
var webpack = require ('webpack'); environment.plugins.append (
'Provide',
new webpack.providePlugin ({
$: 'jquery',
})
) module.exports = environment
This snippet makes Webpack “deliver” the jQuery module to all packs using the name $. This is equivalent to adding the following to the start of each pack:
import $ from 'jquery';
Thanks for reading!