← home

A Year in Frontend Development at Zapier

Prior to October 2011, I hadn't written more than a handful of lines of Javascript. I had done my fair share of HTML and CSS work, but always to support some PHP-driven backend engine. Early on, my co-founder Bryan Helmig and I decided to split Zapier into two parts: the backend and the frontend, which would communicate over an internal JSON REST API. Given the complex nature of Zapier, it seemed elegant to architect the service this way. Bryan focused on writing our backend framework in Django and Python and I decided to foray into some exploratory frontend tech.

The frontend stack which Zapier ran on for twelve months comprised of Coffeescript, Backbone, RequireJS, Mustache, and Sass.

The decision to use this stack was a bit arbitrary. Coffeescript and Backbone were really hot and I thought they'd be worth learning. Other pieces came out of suggestions and early research. The initial demo for Startup Weekend was written in pure Javascript and CSS so the transition to these relatively new pieces of tech had immediate tangible benefits for my sanity.

Some of those decisions were good, some bad. Let's take a look at each piece…


Coffeescript

Coffeescript is a little language that compiles into Javascript. It adds syntactic sugar and new grammars to writing pure Javascript. I would recommend it immediately to anyone who has done more than a weekend's work in pure Javascript. A common theme I've heard is “Begin with Javascript, and once you fully understand it, you can start using Coffeescript”. I disagree with this. I think even relative beginners can reap the rewards of Coffeescript (I certainly did). You don't need to be a “rockstar ninja” to benefit from simpler syntax and fewer gotchas. Two things I would miss if I didn't have Coffeescript:

1. The existential operator

?

The existential operator is a shorthand syntax you can use to absorb and test for null or undefined values. Typing is Javascript is very loose and it is a common pitfall to incorrectly assume a certain falsy type (like an empty string or zero). On top of that, the operator enables you to avoid undefined TypeErrors, like this one:

>> blog = {}
>> alert(blog.author.name)
TypeError: Cannot read property 'author' of undefined

Using Coffeescript we can write it like this, to absorb the null value and avoid a runtime error:

>> blog = {}
>> alert(blog.author?.name)
undefined

The operator also enables you to quickly and correctly test for existence of keys in objects:

>> blog = {author: 'Mike Knoop'}
>> alert(blog.author?)
true
>> alert(blog.posts?)
false

There are 1130 instances of the existential operator in our Coffeescript codebase at Zapier which is a testament to how useful one character can be.

2. Lower effort for well-styled code

My other favorite part of Coffeescript isn't a single feature. But rather the style it enables you to write in. Here's a method I pulled from our frontend codebase:

setCaret: ($el) ->
  return if _.any @NO_CARET_TYPES, (type) => return type == @model.get('type')
  lastCaret = @getCaret()
  caret = $el.atCaret('getCaretPosition') if $el?[0]?
  @model.set {caret: caret, lastCaret: lastCaret}
  $el.attr 'data-caret', caret if caret?
  $el.attr 'data-last-caret', lastCaret if lastCaret?

… and the corresponding Javascript:

setCaret: function($el) {
  var caret, lastCaret,
    _this = this;
  if (_.any(this.NO_CARET_TYPES, function(type) {
    return type === _this.model.get('type');
  })) {
    return;
  }
  lastCaret = this.getCaret();
  if (($el != null ? $el[0] : void 0) != null) {
    caret = $el.atCaret('getCaretPosition');
  }
  this.model.set({
    caret: caret,
    lastCaret: lastCaret
  });
  if (caret != null) {
    $el.attr('data-caret', caret);
  }
  if (lastCaret != null) {
    return $el.attr('data-last-caret', lastCaret);
  }
}

To me, there is a lot of mental overhead to parsing pure Javascript compared to Coffeescript. I'll accept that one can write clean, readable Javascript (just go check out the Backbone.js source code) but you have to put in a lot more effort to do so. At minimum, there is a larger learning curve to writing well-styled Javascript over well-styled Coffeescript. For a startup product in active development, the efficiency and speed versus maintainability tradeoff is worth a lot.


Backbone

Backbone is a frontend “MVC” organization framework. If you're familiar with Python web frameworks, Backbone is like Flask, not Django. The spirit of the framework is similar. You'll be rolling your own patterns and might even be overriding built in behavior for large projects. Because of this, Backbone tends to have a shallow learning but steep mastery curve. In my opinion, the mastery curve is steeper than than other frontend frameworks like Ember.js and Angular.js who impose more strict conventions (and thus, less options, less trial and error). It's incredibly easy to whip up a quick Backbone-driven app or site but even easier to fall into common traps as you grow and scale.

Decent patterns are the hardest things to pick up when learning to write sane Backbone-driven applications. One year after implementing Backbone in production at Zapier, I re-wrote our entire frontend to avoid some common pitfalls and implement several patterns I learned over the previous twelve months. Even so, I am surprised how stable our site was during that one year learning period – a testament that “okay” Backbone apps can be written even if you have no idea what you're doing.

Here are a few takeaways from my experience writing and designing the Zapier frontend and subsequent re-write to fix all my mistakes:

1. Do you like creating conventions? You'll love Backbone

Part of the challenge of Backbone (for me) is that hardly anything is strictly imposed. Views are left entirely up to the implmentor. The bootstrapping process is flexible. Model storages can by interchanged on the fly. I mentioned it above, but if you like Flask (python) or Sinatra (ruby), I suspect you'll like Backbone. This is entirely up to taste and preference. I like the allowed creativity, but others prefer more guidelines.

The other consideration is team size. I am a single maintainer so I have the flexibility at this point to throw away patterns and re-implement them. This much harder to do with multiple contributors.

2. Don't create crazy inheritance layers on top of Backbone

It is tempting to do this and it bit me pretty hard. You could probably also go overboard with compositions, but I think it's harder to do so. I originally used several layers of models and collections to do things like:

In most cases it was a better decision to break the pieces into their own models or collections and composite the final object together. A nice pattern for doing this is Underscore's extend method:

Book = new Backbone.Model.extend()

Paginate =
  page: 0
  turnPage: () ->
    @page += 1
    @fetch {data: {page: @page}}

book = _.extend new Book(), Paginate
book.turnPage()

I still use inheritance for a few things and it has it's place. I find myself preferring composition these days, though, mostly to avoid subtle ordering bugs around calling super that seem to creep in when you use inheritance heavily.

3. Use many controllers and make them more than just dumb entry points

Our original app had a single controller. It contained every frontend route and was pretty much a dumb pipe between a route and instantiating a top level view, nothing more. It looked very much like a urls.py from Django-land.

The downside to this approach for a larger mutli-page application was that I started to have many of these specialized top level views. More and more “loading” logic wound up in these specialized views and began to leak downward into child views. The concept of a “view” started bleed together and it was hard to keep everything straight.

Now there exists a separate controller for each section of our application. A single controller handles the model initialization and view rendering for its section of the app. This is extremely clean compared to what I was doing and enabled me to implement a really lightweight convention (read: copy-paste template) for new controllers that wanted to ensure models existed before the rest of the page rendered.

4. Zombie-views, Race conditions, Event-callback hell (and more to come)

Dedicated posts on these topics are in my future. Keep an eye on my RSS feed for more.


Sass

Sass is an intermediary language for CSS which lets your write CSS using nested rules, mixins, math, and variables. I have really nothing to complain about with Sass. You should use it (or a similar CSS pre-parser).

One cool trick I discovered pretty late into my learning curve is that you can @extend existing CSS classes into other CSS classes. It looks like this:

button {
  color: black;
  .success { color: green; }
  .error { color: red; }
  .disabled { color: gray; }
}

.homepage {
  // make the home page call to action green
  .call-to-action { @extend button.success; }
}

RequireJS (& AMD)

This one is pretty negative. Let me start with what is good. RequireJS has really solid documentation. The maintainers are active and willing to help. Many popular libraries include native AMD support now and there is a fallback shim you can implement directly with RequireJS if AMD support isn't available in your favorite library.

Now, the bad. Put it simply: RequireJS is way too complex. The promise of RequireJS (and by extension the AMD module format) is that it will improve the speed and quality of your code. After twelve months with RequireJS, I can say this was not the case.

I estimate I put in between fifty and one hundred hours learning, using, debugging, deploying, and optimizing RequireJS over the course of one year. I seriously tried to make it work. I even hacked the Coffeescript compiler to add a nice syntax layer for RequireJS. But there are so many drawbacks to using it and no gains over alternatives. Some of the reasons why I no longer use it:

define([
   'jquery',
   'use!underscore',
   'use!backbone',
   'mustache',
   'text!template/header.html',
   'text!template/loggedInLinks.html',
   'text!template/loggedInTempLinks.html',
   'text!template/loggedOutLinks.html',
   'views/home/__init__',
   'views/modal/__init__',
   'views/modal/login',
   'views/modal/loggedIn',
   'views/modal/signup',
   'views/spinner',
],
($, _, Backbone, MustacheWrapper, HeaderTemplate, LoggedInLinksTemplate, LoggedInTempLinksTemplate, LoggedOutLinksTemplate, HomeView, ModalView, LoginModalView, LoggedInModalView, SignupModalView, SpinnerView) ->
  return # everything has to be indented one line

Modularization is a great idea. Backbone encourages this format and it's a huge organizational win to split all your files up. The real problem is I couldn't obtain any tangible benefits after using it for twelve months in production. We're now using a super-simple asset pipeline (Django Pipeline; look for Jammit if you're on Rails) and by comparison, I can't believe how much effort I put in to make RequireJS work to achieve practically the same thing (frontend asset compilation, minification, and deployment).

If a reasonably smart fellow can't figure out any benefit from your software after a year, something is broken. If you're smart enough to figure out and master RequireJS while keeping your sanity – all the power to you. I will happily admit that I was not smart enough to do so.


Mustache and Handlebars

Some members of the community take a passionate stance against Handlebars. I have used both in production and don't really have a preference between the two. Here's the comparison:

Mustache

A simple logic-less templating language. It has plugins available for practically every programming language and is really easy to learn. Mustache-style templating is so simple and flexible it can power both our frontend HTML templates and our live Zap previews! The syntax is so simple I didn't really pick up any extra tricks in production beyond just reading the documentation and implementing it. And I think that is a powerful statement. It really is as simple as it sounds.

Handlebars

Handlebars is almost a drop-in replacement for Mustache (you'll have to replace your if/else/then blocks). The big thing Handlebars gives you over Mustache is the ability to implement custom helpers. Normally in Mustache if you needed to implement some special rendering logic, you would pass in a Javascript function which performed the logic and returned a string. This is similar to Handlebars custom helpers, except custom helpers can be re-used globally.

In Handlebars, you can register a custom helper method like this:

# accessor: returns the article "a" or "an" depending on the context string. Eg. "Computer" will return "a"
# and "Elephant" will return "an". Can pass a hash option `capitalize` which will capitalize the article.
# usage: {{#article}}string{{/article}} outputs "a string"
Handlebars.registerHelper "article", (options) ->
   # options:  pass `capitalize` true to get a capital article
   string = options.fn(@)
   vowels = ['a', 'e', 'i', 'o', 'u']
   vowel = false
   letter = string.toLowerCase()[0]
   vowel = true for v in vowels when letter == v
   article = 'an ' if vowel
   article = 'a ' if not vowel
   article = _.str.capitalize(article) if options.hash?.capitalize
   return article + string

And you can then use it in your template file like this:

<div>There is {{#article}}{{animal}}{{/article}} in the middle of the room.</div>

<!-- context passed as {noun: 'elephant'} -->
<div>There is an elephant in the middle of the room.</div>

<!-- context passed as {noun: 'mouse'} -->
<div>There is a mouse in the middle of the room.</div>

The gotcha with Handlebars is you're tempted to re-implement Jinja2-style template yourself. I recommend not doing that, keep it simple. Use it only for repeatable helpers you find yourself needing across template files. Another trick is deciding whether to pass the real Backbone object into the template or pass a JSON representation of the object.

I tend to prefer the JSON representation but there are certain helpers I've written that expect Backbone objects so they can call methods on them. To compromise, you could pass in the raw Backbone object and access the JSON as myObject.attributes inside the template.

The jury is still out for me on Mustache versus Handlebars.


Final thoughts

I'll likely deep dive into many of the above topics in further blog posts so stay tuned to my RSS for more on that front. I am really happy with where we're at today, frontend tech wise, at Zapier. We have a really solid foundation for growing and building the interface going forward. There is still a lot to learn, of course. I'd love to discuss anything mentioned or alluded to in this post. Drop me a line directly or start up a comment thread below.