Blog

The main Rails project of Neighborly has today more than 4 years old. Many people in the team invested a good amount of time trying to get the best front end using Rails’ conventions.

The framework advises you pick between two different ways of using external libraries:

  1. Saving the source in vendor/assets.
  2. Requiring a RubyGem that will end up doing that for you, while benefitting of Bundler versioning.

Honestly though, manually keeping dependencies updated sucks. If you are not doing it through GitHub, you are updating a RubyGem. Yet, vendorized versions aren’t usually expected to have their dependencies loaded without modules.

There is already a community solving these problems, so why would we struggle to keep in the Rails way? Fitting into them takes time, so have to consider if it’s worth the periodical headache.

One way of reaching that goal is with a better separation between Rails and JavaScript. Preferably, without the burden of creating and maintaining distinct applications.

Our solution

Sprockets -> webpack

Framework-agnostic. Doesn’t matter if you’re using Ember, React or plain JavaScript. It works.

RubyGems -> NPM

For being JavaScript only, receives more focus from JS community. Most often updated compared with RubyGems.

ECMAScript 5 -> ECMAScript 6

Provides modularization and feels like CoffeeScript. Out of the box.

Server side rendering of React components

Embrace Virtual DOM and web components with no penalty for clients.

Sprockets ❤️

It’s still responsible for the final minification and caching.

Rails ❤️

app/assets is still used to keep all the assets.

Heroku ❤️

git push heroku master handles the whole deployment.

Setup

We are going to turn your app/assets into an NPM package, having its own dependencies and setup. Make sure you have NPM installed before starting.

$ npm init  
$ npm install webpack -g  
$ npm install babel-loader expose-loader webpack-dev-server —save-dev

Remember to tell your colleagues about getting webpack binary, by running its install command (and after, installing NPM):

bin/setup

...  
system "npm install webpack -g"

Also, to ease first installs — and further deployment — you can tell NPM to pack the current version of the assets every time you call npm install.

app/assets/package.json

{  
  ...  
  "scripts": {  
    "postinstall": "webpack —config webpack.config.js"  
  }  
}

By npm installing you will also see a new app/assets/node_modules folder created.

webpack

webpack will own your front end environment. It’s responsible for transforming your raw JavaScript files — React, ES6… — into *.js, making sure libraries are also included, adding some flavor to your days. You can actually start enjoying doing front end again!

Since everything will be packed before getting into Rails, just require webpack’s bundle in the application.js file.

app/assets/javascripts/application.js

//= require webpack.bundle.js

For webpack, we can choose Babel as the first addition to our stack. From now on, we can write JavaScript using ECMAScript 6.

app/assets/webpack.config.js

module.exports = {  
  context: __dirname + '/javascripts',  
  entry: './webpack_application.js',  
  output: {  
    filename: 'webpack.bundle.js',  
    path: __dirname + '/javascripts',  
  },  
  module: {  
    loaders: [  
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}  
    ]  
  },  
  resolve: {  
    extensions: ['', '.js', '.jsx'],  
    modulesDirectories: ["node_modules", "javascripts"]  
  },  
};

app/assets/javascripts/webpack_application.js

// From here it's with you. This is your new "application.js"

But wait! Don’t forget to gitignore files downloaded and generated by webpack.

.gitignore

...  
app/assets/node_modules/  
app/assets/javascripts/webpack.bundle.js

Travis CI

Nothing so new here, just provision the machine with Node.js and cache front end libraries located in the app/assets/node_modules..

.travis.yml

language: ruby  
node_js: 0.10  
cache:  
  bundler: true  
  directories:  
    - app/assets/node_modules  
before_script: ./bin/setup  
script: bundle exec rspec —colour —format d

React

If you plan to use React, you will have to make some additions to the setup. First of all, add it as a dependency.

$ npm install react —save

Later, we have to configure webpack and make Babel understand and listen for changes also in *.jsx files. For that, change lines referring to babel-loader and resolved extensions.

app/assets/webpack.config.js

...  
      { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'}  
...  
    extensions: ['', '.js', '.jsx'],  
...

I would also highly recommend checking the project react-rails. This way, your server will be able to provide valid HTML of the components even when the client has no JavaScript active.

Tell react-rails the location of your components file and everything should work already. If doesn’t, it’s probably because you are using libraries with problems on running outside a full browser (nowadays, “flash-detect” is an example).

config/application.rb

...  
    config.react.component_filenames = ['webpack.bundle.js']  
...

Development environment

Another process will be added to your development workflow. If you use foreman to manage a Procfile, you can add webpack to a development version of it. This will monitor your assets folder for any changes and, after noticing any, will compile just the difference from the latest output files.

$ gem install foreman

Procfile.development

...  
webpack: webpack -wc —config app/assets/webpack.config.js

Start foreman and open the browser. Development environment set up.

$ foreman start -f Procfile.development

Deployment

One of the better things about Heroku is its git push/done workflow. You don’t need to have a local machine with the whole development environment working, just git push heroku master and the project is built in the production server. Our deployment doesn’t break this.

The idea is that we have Heroku detecting our application as containing two distinct apps: a Node.js and a Ruby on Rails. First, the Node.js one, to install front end dependencies and use webpack to build files readable by Sprockets. Later, Rails takes over expecting to find specific files in app/assets and there’s a happy ending.

.buildpacks

https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/heroku/heroku-buildpack-ruby

In the terminal:

$ heroku config:set BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

Here is a little trick: since Node.js’ Heroku buildpack expects to find the package.json in the root path, a node_modules with dependencies would also be created in the root. Since we are willing to respect Rails’ conventions and keep assets inside app/assets, we created a package.json ease the access to the one in the right folder.

package.json

{  
  "name": "frontend-deployment",  
  "version": "0.0.0",  
  "private": true,  
  "engines": {  
    "node": ">= 0.10"  
  },  
  "scripts": {  
    "postinstall": "cd app/assets && npm install"  
  },  
  "author": "Neighborly Corporation"  
}

Future

From now on, you have freedom to create your own structure inside app/assets, respecting the environment existent before. webpack_application.js is the only connection between back end and JavaScript, allowing a better separation of tools and code.