Replacing Sprockets with Webpacker for JavaScript in Rails 5.2

· , , ,

webpack has become an increasingly popular choice for JavaScript bundling and compilation. In addition to simplifying the use of transpilers like Babel and Bublé, webpack also provides more advanced functionality such as code splitting, tree shaking, and hot module replacement.

With the release of Rails 5.1 and the Webpacker gem, using webpack with Rails became much easier, and in Rails 6, Webpacker will replace Sprockets as the default JavaScript compiler.

I recently replaced Sprockets with Webpacker in one of my Rails 5.2 apps and wanted to write this guide in hope of making the switch easier for someone else.

As a summary of the instructions and to provide additional context, each section of this guide links to a commit in an example application on GitHub.

Prerequisites

In addition to Ruby and Rails, using Webpacker also requires Node.js and the JavaScript package manager Yarn. Below are the minimum versions required by Webpacker. Between parentheses are the versions I used when writing this post:

If you’re running macOS and have Homebrew installed, you can install Node.js and Yarn using the following command:

brew install yarn

Installing Webpacker

If you generated your application with the --webpack option or if you’ve already installed Webpacker on your own, feel free to skip ahead.

The first step is to add Webpacker to your Gemfile:

gem 'webpacker', '~> 3.5'

Use Bundler to install the gem and run the Webpacker install task:

bundle
bundle exec rails webpacker:install

This will generate a bunch of new files in your project and then use Yarn to install Webpacker’s JavaScript dependencies.

One of the files Webpacker generates is app/javascript/packs/application.js. This is your application’s JavaScript pack and it will soon replace app/assets/javascripts/application.js.

Add this tag to app/views/layouts/application.html.erb and any other layout to load the JavaScript pack generated by Webpacker:

<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

If you visit your application in a browser you should now see a message saying Hello World from Webpacker logged to the console.

calleerlandsson/webpacker-rails-5-2@da93aeb provides a summary of these instructions and shows the files generated by Webpacker’s install task.

Loading Rails’ JavaScript libraries

By default, Rails 5.2 uses Sprockets to load the following JavaScript libraries:

Let’s start by loading the first three of these using Webpacker instead.

First you’ll need to add the corresponding npm packages to the project:

yarn add @rails/ujs turbolinks @rails/activestorage

Then you need to load them in the JavaScript pack. Do so by adding the following lines to app/javascript/packs/application.js:

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()

To finish up, remove these Sprockets require statements from app/assets/javascripts/application.js:

//= require rails-ujs
//= require activestorage
//= require turbolinks

See calleerlandsson/webpacker-rails-5-2@d8949d7 for a summary of these changes.

Loading jQuery

This step is only required if your application actually uses jQuery. If it does, you need to make sure jQuery is available in the JavaScript pack before you can start moving application code over.

Start by adding the jquery package to the project:

yarn add jquery

To avoid having to import $ from 'jquery' in every file that uses jQuery, make the following change to config/webpack/environment.js:

 const { environment } = require('@rails/webpacker')
 
+const webpack = require('webpack')
+environment.plugins.append('Provide', new webpack.ProvidePlugin({
+  $: 'jquery',
+  jQuery: 'jquery'
+}))
+
 module.exports = environment

calleerlandsson/webpacker-rails-5-2@57a76f7 makes these changes to the example application.

Loading ActionCable channels

Feel free to skip this step if you’re not using ActionCable.

Start by adding the actioncable package to your project:

yarn add @rails/actioncable

Create an app/javascript/channels/index.js file with the following content:

const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)

This will automatically load any file with a name ending in _channel.js within the app/javascript/channels/ directory and all of its subdirectories.

To load the app/javascript/channels/index.js file, add the following line to app/javascript/packs/application.js:

require("channels")

Move your channels from app/assets/javascripts/channels/ to app/javascript/channels/ and make sure their file names end with _channel.js.

By default, ActionCable channels use the App.cable consumer created in the app/assets/javascripts/cable.js file. To avoid polluting the global namespace you can use webpack’s module system to provide a consumer for your channels.

To do so, create a file named app/javascript/channels/consumer.js with the following content:

import { createConsumer } from "@rails/actioncable"

export default createConsumer()

Then update your channels to use this consumer instead of App.cable:

import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  // ...
})

You should now be able to remove the app/assets/javascripts/cable.js file.

calleerlandsson/webpacker-rails-5-2@fb1e73e summarizes these instructions and shows an example of updating a channel.

Loading application code

Now it’s time to move all of your application JavaScript code from app/assets/javascripts/ to app/javascript/.

To include them in the JavaScript pack, make sure to require them in app/javascript/pack/application.js:

require('your_js_file')

See calleerlandsson/webpacker-rails-5-2@1a4e049 for an example.

If your application code depends on other JavaScript libraries, you’ll need to add the corresponding npm packages to your project and import them in your JavaScript files.

Cleaning up

You should now be able to remove the javascript_include_tag for app/assets/javascripts/application.js from all of your layouts:

<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

Delete the app/assets/javascripts/ directory and remove this line from app/assets/config/manifest.js:

//= link_directory ../javascripts .js

If you were previously using jQuery from the jquery-rails gem you can remove it from your Gemfile. Since you’re no longer using Sprockets for compiling JavaScript you can also remove the coffee-rails and uglifier gems, as well as this line from config/environments/production.rb:

config.assets.js_compressor = :uglifier

These changes are made to the example application in calleerlandsson/webpacker-rails-5-2@7712622.

Trying it out

Click through your application in a browser and try out different features while checking the console for errors. Make sure all your system tests still pass.

If you run into problems following this guide and come up solutions that could help someone else, let me know so I can share them here.