Related articles

No items found.
The French newsletter for Ruby on Rails developers. Find similar content for free every month in your inbox!
Register
Share:
Blog
>

How to write Javascript in Rails 6 | Webpacker, Yarn, and Sprockets

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

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

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:

  • You usually add a gem that provides it, then you required it in

app/assets/application.js (which was compiled by Sprockets)

  • Now you need to add it using Yarn ():

 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

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.

Babel

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.

Webpack

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:

  • take your ES6 Javascript code,
  • Use the Babel-loader for Babel to compile ES6 Javascript code into ES5,
  • then output the resulting package into a file that you can include in your HTML DOM (<script type="text/javascript" src="path-to-es5-javascript-pack.js"></script>).

Webpacker

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)
  • You can include a Javascript pack in your views using
    <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/.

Sprockets 4

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:

  • used to list the assets available in config.assets.precompile (Sprockets 3, Rails 5)
  • Now you have to do it in a manifest file app/assets/config/manifest.js (Sprockets 4, Rails 6)

If you want to include an asset from the Sprockets pipeline, you'll need to:

  • Write your CSS (for example: app/assets/stylesheets/my_makeup.css)
  • Insure 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)
  • Include it in your view using stylesheet_link_tag (<%= stylesheet_link_tag 'my_makeup' %>)

Don't try to use Webpack like you would with Sprockets!

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:

  • With Sprockets: views can interact with what your JS files expose (use a variable, call a function,...)
  • With Webpack: Views do NOT have access to what your JS packs contain.

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.

You can always use Sprockets for Javascript code.

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.

  • Create the app/assets/javascripts directory (note that the javascripts are in the plural here)
  • Update app/assets/config/manifest.js as a result (//= link_directory.. /javascripts.js )
  • Include your Sprockets Javascript files in your views using javascript_include_tag (notice the difference:javascript_include_tag
    for Sprockets,javascript_pack_tag for Webpacker)
  • Do your thing.

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.

How do I add bootstrap 4 and fontawesome 5 to a Rails 6 application?

To help you better understand, I advise you to apply as you read. This will help you a lot to understand these new features.

1. Create a new Rails 6 application

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:

  • package.json

Webpacker:

  • config/webpacker.yml
  • app/javascript/packs/application.js
  • app/views/layouts/application.html.erb

Sprockets:

  • app/assets/config/manifest.json

2. Add a root page

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.

3. Add required Yarn packages

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.

4. Include the JS part of bootstrap and fontawesome

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.

5. Include the CSS part (S) of bootstrap and fontawesome

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>
</a>
To app/views/Welcome/index.html.erb, run your rails server and make sure the primary button and icon appear correctly.

Make jQuery available in all packs

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!