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:
- Ruby 2.2 (2.6.1)
- Rails 4.2 (5.2.2)
- Node.js 6.14.4 (11.9.0)
- Yarn 1.x (1.13.0)
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.
calleluks/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:
- rails-ujs, the unobtrusive scripting adapter
- ActiveStorage for handling file uploads
- Turbolinks for speeding up navigation
- ActionCable for implementing real-time features using WebSockets
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 calleluks/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
calleluks/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.
calleluks/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 calleluks/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 calleluks/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.