Kapost Engineering

Recent Posts


Recent Comments


Archives


Categories


Meta


Tooling Choices for a Rails/React Application

Alex McPhersonAlex McPherson

Rails and React

Introduction

Rails is a mature old beast that is still quite suitable for rapid development of medium-scale web applications (believe it or not!). It falters a bit though on its Omikase solutions for rich behavior on the clientside. Turbolinks is a dud and Sprockets is an opaque, slow mess that cuts you off from easily using the biggest module repository in the world without wrapping each bit of code in its own gem.

So how do we attend to the needs of a rich clientside app while still using one of the best server side solutions out there? Throw more JavaScript at the problem!

I’ll go through the major needs of a modern (2015, could go out of date next year) Rails/SPA application and why Kapost made the choices it did.

Tooling Categories

Clientside library

Kapost is building more and more single page applications driven by our healthy Rails APIs. The interactions on these pages are sometimes quite simple, but often complex and stateful. We considered (and did research spikes with) EmberBackboneKnockout, and React. The clear winner of hearts and minds of the developers was React.js in a landslide.

Our older frontend code was in a proprietary quagmire of Backbone and Knockout glue code that was hard to hire for, hard to train up on, and hard to reason about. We found ourselves writing many ‘abstract models’ to hold UI state and facilitate complex interactions, and it was hard to track what lived on the server and what was client side only data.

When we started comparing our historical UI challenges to the Flux pattern advocated by Facebook, we saw all of our problems fit perfectly. We tried this mental exercise for a few weeks in our spare time: “What about when we… Oh yeah, that works perfectly!” Not only did this process assess the framework’s suitability for our needs, it was also good training to get the whole team thinking in terms of Flux before even writing a line of code.

Fits just right…

As a team lead I wanted to make sure we weren’t missing out on any existing Flux implementations, so I explored the area by writing the same application in several libraries. I looked in to Marty.jsAlt.js, and Fluxxor. They all had unique differences, but in the end I decided that none provided distinct value over a vanilla Flux app using the Facebook Dispatcher. Their best ideas were organizational patterns and helpers that can be adopted piecemeal when we have the problems that they solve, and not using a prebuilt-framework avoids lockin in a fast moving area where stagnation and abandonment seems likely as things shake out over the next year or so.

Package Management

Pasting code into a vendor folder is not an appropriate way to manage JavaScript. You lose versioning, can’t easily update, and your git repository becomes bloated by doing this. If almost all of your application will be written in the language, best to not treat it as an afterthought.

The two contenders for managing frontend dependencies were npm and Bower.js. I chose npm because its ecosystem of modules is far larger and it allows you to use Common.js module syntax, which is very legible. It also provides the amazing functionality of the package.json file, which I’ll discuss in later sections. In the past I had struggled with actually getting Bower-managed packages into my application, but with Common.js modules there are two obvious and powerful solutions.

Building

The two best ways to get your JavaScript, as Common.js modules, into your browser are webpack and Browserify. These are tools I get to use easily as a benefit of having chosen npm in the Package Management department. Both solve the same problem: they create a dependency graph of your modules then resolve that graph into a big ‘bundle’ of JavaScript where each module gets its dependencies handed to it.

Webpack has some additional niceties in its ecosystem: an easy to use dev server, asset and image dependency resolution (require(modal.css)), and a rich suite of ‘loaders’ that let you say things like “inline this PNG as B64 if it’s less than 1k in size“. It is also advocated by the React team at Facebook, which I hoped would reduce tooling friction overall.

One killer feature of webpack that tipped the scales in its favor was the ability to resolve common chunks. What this means is that instead of starting your dependency graph at one point (say, index.js), you can specify several. Perhaps one for each main page you serve. Webpack will then produce one ‘bundle’ of output for each page, BUT it will also produce one more: the commonalities between each page. This lets your users fetch only once your Reacts, your moment.js, and your modal libraries, and they can then lazy-load the page-specific JavaScript as they visit each subsequent page. This approach has been advocated for years but until now has required too much manual effort to find those efficiencies for widespread adoption.

Common chunk extraction by webpack

We at Kapost are not using the asset dependencies of webpack just yet (fonts, css, images) but I’m happy to know that the tool is one we can grow into as our projects mature.

Webpack output example:  

Testing

Testing frontend JavaScript can be tricky, and adding more tools to the pipeline can make it trickier. One of the nice things about Flux is that a lot of your logic is baked into your stores, which are plain old JavaScript objects. These are quite easy to test using node as they have no DOM dependencies.

Our current solution is to heavily test our stores and components using jsdommochachai, and rewire. Here are some examples of the assertions you can make using our combination of tools:

//storeSpec.js
describe("#setFavorite", function() {
  it("should set favorite status for content", function() {
    var content = contentMock[2];

    contentStore.setFavorite(content.id, true);
    content.favorite.should.equal(true);
  });
});

//componentSpec.js, using sweet ES6 syntax
import React from "react/addons";
import jsdom from "mocha-jsdom";
import {expect} from "chai";

//See https://facebook.github.io/react/docs/test-utils.html for available methods
let {TestUtils} = React.addons;

import TitleBar from "js-app/components/titleBar";


describe("TitleBar", function() {
  jsdom();

  it("should have the title in its DOM", () => {
    var titleBar = TestUtils.renderIntoDocument(
      <TitleBar title="My Gallery" />
    );

    var titleTag = TestUtils.findRenderedDOMComponentWithClass(titleBar, "gallery-title");

    expect(titleTag.getDOMNode().textContent).to.equal("My Gallery");
  });
});

These

 are invoked using a watch command and use the OS X notification center, so failing builds are immediately brought to the developer’s attention.

Image of failed build

Automation

All this sounds a lot harder than rails s and slinging some code!

Well… yes. There is generally a rails server, a webpack compiler, and a test task all running at the same time. How to you remember the flags for all of this? Seems hard.

npm has a special ability similar to rake, where you can define scripts for it to run. We use this heavily, and it lets us avoid introducing a build tool like Grunt or Gulp to our workflow:

//package.json snippet
{
  "name": "app-name",

  "scripts": {
    "webpack": "$(npm bin)/webpack --devtool cheap-module-eval-source-map --progress --colors --watch",
    "webpack-prod": "$(npm bin)/webpack -p",
    "test": "mocha --compilers js:babel/register",
    "test-coverage": "mocha --compilers js:babel/register -R html-cov --require blanket > coverage.html && open coverage.html",
    "test-watch": "mocha --compilers js:babel/register -w",
    "postinstall": "npm run webpack-prod"
  }
}

So

 we’re able to type things like npm run test-watch and it will start that task for us. Some are automatically run by npm as lifecycle hooks (postinstall) and by CI services (npm test) and are “special”, but you can also make your own to help yourself out.

Conclusion

Front end tooling is moving quite quickly, so we chose a minimal set of tools that we felt would grow with our needs as our products grew over time. Let us know what you use!

Alex is an engineering lead at Kapost in Boulder, Colorado. His past experience includes financial information design and custom software development. He has taught JavaScript and Ruby on Rails at Colorado University’s BDW program and helped create custom training for various clients. When he’s not engaging in horrific amounts of screen time he is usually racing mountain bikes, shaking fancy cocktails, or eating kale with his wife and their new daughter.

Comments 1