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 intopublic
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
.
Run npm init
and follow the instructions. Next, install webpack and
babel-loader by running npm install --save babel-loader 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" }
]
}
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.
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.
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.
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.
So, I read this quickly and almost missed that this was written for Rails 4, so that may be the reason for your approach here but, I have a few questions about this configuration you've chosen.
Are you using the asset pipeline for the Webpack transpiled ES6 because there isn't the
image_pack_tag
orjavascript_pack_tag
helpers available in Rails 4?Doesn't Webpack already minify it's output? It seems like putting the output into the assets pipeline to re-minify/process the already processed JS seems unnecessary and you could just put the transpiled output into your Public folder.
Have you made anything so that when running in development and making changes to the JS, Rails will know that it's JS assets need to be transpiled and will trigger Webpack? Since this is available in newer rails apps (out of the box this is how it works in Rails 6) so I was just curious if you'd made any adaptations to get the same behavior.