Webpack with Rails Part 1

I don't like Sprockets, but it's the easiest and best choice for lots of Ruby on Rails projects. When looking for a better way to manage my JavaScript assets and an improved developer story, my current tool of choice is webpack. While I wish Rails supported drop-in asset pipelines the way that Phoenix does, it's not hard to use webpack with Rails.

Disclaimer: There are lots of other guides for getting webpack working with Rails. Those guides I've read missed details I felt were important, or configured webpack in ways that I was not happy with. I wrote this as a guide to provide the reader with a solid starting point that wasn't too opinionated.

I had the following requirements:

  • I wanted Babel support; it makes working with JavaScript far more enjoyable.

  • I didn't want my "source" assets to be in a folder that Sprockets manages; while some might prefer to have these files in app/assets/javascripts I don't think Sprockets should know anything about them.

  • I wanted the entries output by webpack to be in a subfolder of app/assets/javascripts rather than straight into public so that they get fingerprinted to avoid caching issues.

This article was written using Rails 4.2.4 and should work for any version of Rails 4.2, and should need little if any modification for working against other 4.x versions. I used webpack 1.12.2, and babel-loader 5.3.2.

Install webpack

Run npm init and follow the instructions. Next, install webpack and babel-loader by running npm install --save babel-loader webpack.

Configure webpack

Here's a minimal webpack.config.js. Drop it into the root of your project.

// webpack.config.js

"use strict";

var path = require('path');
var config = module.exports = {};

config.context = __dirname;

// Specify your entries, I store all my webpack managed JavaScript in
// app/webpack, as per my earlier requirements.
config.entry = {
  cart: './app/webpack/cart.js'
};

// This outputs an entry named 'foobar' into
// app/assets/javascripts/entries/foobar.js.
config.output = {
  path: path.join(__dirname, "app/assets/javascripts/entries"),
  filename: "[name].js"
}

// Use babel-loader for our *.js files.
config.module = {
  loaders: [
    { test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader" }
  ]
}

Rails time

Now, if you write some ES6 in app/webpack/cart.js you can run node node_modules/.bin/webpack and your entry will be compiled to app/assets/javascripts/entries/cart.js. (You may need to create that folder.)

// app/webpack/cart.js

// Write some ES6!
let foo = "bar";
console.log("bar");

To include this entry in your Rails views/layouts, you'll need to open up config/initializers/assets.rb and add entries/cart.js to your procompile list, and then restart your rails server.

# config/initializers/assets.rb

Rails.application.config.assets.precompile += %w( entries/cart.js )

You can include the javascript tag as you would any other, <%= javascript_include_tag "entries/cart" %> if you're using ERB.

Finishing touches

Since you're far too lazy/efficient run webpack every time you modify your assets, you want to run the watcher. Open up your package.json and add a scripts entry:

// package.json

{
  // ...
  "scripts": {
    "watch": "webpack --watch --colors --progress"
  },
  // ...
}

This allows you to start the webpack watcher by running npm run watch. Make sure to include this in your README so new users on the project know how to get things working.

Deployment

I won't include any specifics for handling deployment as the details would vary too much between environments. In most configurations you'll want to either override/modify your assets:precompile rake task to run webpack first, or modify your deploy system to run webpack before assets:precompile is run.

Write ES6, Be Happy

Webpack and Babel provide a powerful system for breaking up your JavaScript into self-contained entry points and isolating individual components, with an excellent developer story.

Webpack allows you to build much more complex pipelines to meet your application's requirements. For example Webpack's ecosystem works exceptionally well when building React and Flux/Redux applications, offering tools like React Hot Loader.