tourRouter
- An app to help agents/assistants/artists/managers collaborate on all aspects of routing a tour around the world
tourRouter
- Single page app (SPA)
- App is served by Node
- Built on a Node API
- Just an API Client
- Deployed to Azure
- Node API + client is all one language, makes developers more productive
- App as an API client is important because it serves as a sanity check to make sure we are properly separating concerns. It also allows us to plan for the future as we could have multiple clients and cross-service apps.Client Stats
-
9900 lines of custom JS in 113 files
-
1700 lines of jade in 43 files
-
55.1k lines (1.8MB) of clientside JS
-
3KB of HTML
- Gzips/minifies down to 218KB
- A lot of complexity in a single page app
- We have put a lot of thought into how to keep the development process sane without sacrificing production quality or speed (and vice versa)
- Single page apps can devolve into a mess of code very quickly.
- And what does that look like...Our HTML
<!DOCTYPE html>
<html>
<head>
<title>tourRouter</title>
<!-- Meta tags... -->
<!-- Splash images... -->
<!-- Favicons and app icons... -->
<link rel="stylesheet" href="/app.fd504f36.css">
<script src="/app.647efd62.js"></script>
- This acts as our cache manifest file. I'll show you how later.
- The importants bits are the css and js files, each named uniquelyThe Team(s)
API & OpsLos Angeles
Web App: 2 devsWashington, Arizona
- Clear separation of concerns between teams
- Async communication because of remote
- Real app with real deadlines and stakeholders
- We needed to ship often, rapidly iterate and make deadlinesThe Front-End
- Moonboots is a set of conventions on the server for SPAs. It provides what we need to do caching, minifying, concatenation of assets as well as helpers for routing. It's also very environment aware with sane defaults for development and production.
- Browserify powers much of moonboots and gives us module on the client.
- npm is where we try to get all of our JS from
- Human Javascript is patterns of large client side apps. Powered by backbone it provides extensions for models, views and collections and helpers to connect all three.
- The importants part isn't the tech, but the patterns the tech allows us to follow easilyDev Process
-
Don't sacrifice for production performance
-
Guaranteed cache priming/busting
-
Fast iteration loops on
local
mobile
development
staging
production
- I've been on project where production was what everything was optimizied for, to the point that developers had to jump through hoops to develop locally.
- Fast iteration (for all environments and deviced)
- How long does it take me to reload this page?
- How long until I can see this against real data?
- How long until I can test on my iPhone?
- Local === seconds, Dev === minutes, Staging === 10 minutes, Production === 30 minutes
- Guaranteed caching
- Did my change show when I just refreshed? If not, how can I be sure it will next time?
- One of the biggest pains as a developer is when you don't trust the process. I've been on projects where I was never sure if when I refreshed if I was seeing my latest changes. This conditioned me to worry about that and not on my code.Dev Environment
- Switches on process.env.NODE_ENV
- Concatenates non-CommonJS files
- Builds a Browserified bundle
-
onBeforeJS & onBeforeCSS hooks
- Moonboots is very optimized for developer environment without sacrificing anything in production
- NODE_ENV allows us to easily switch defaults (minify, cache, etc) based on environment
- Support for CommonJS incompatible files (jQuery, jQuery plugins) is nice (but we like CommonJS stuff a lot)
- Browserify provides the magic of an entry file, custom modules, and 3rd party modules into one bundle
- We run a fn to build jade -> vanilla JS functions in the hook
- Build on each request guarantees dev is never looking at stale codeUse a Module Pattern
- We use CommonJS/Browserify
- Others use AMD/RequireJS
- Use something
-
Structure
collections
views
models
helpers
pages
templates
as modules
-
Saner
development
refactoring
reuse
overall process
- CommonJS (and Browserify) fit our use case very well. The build step is seamless and unknown to the developer.
- We structure everything as modules
- Makes it very easy to spec out a model/view/template and refactor later into more reusable partsUse npm
- Install clientisde code from npm
-
npm install
backbone
async
lodash
moment
--save
- Moonboots can handle anything else
- Send Universal Module Definition PRs :)
- As much as possible we try to get clientside libraries from npm
- Reusable between server and client
- Extremely easy to find and get a module into your dev and production envs
- We are not restricted. Moonboots can handle non CommonJS modules. But it is so much nicer to use all code from npm.Clientside Modules
From Our Code
var
BasePage
geospatial
ShowModel
templates
=require('
../pages/base
../helpers/geo
../models/show
../templates
')
From node_modules/
var
_
Backbone
moment
async
=require('
lodash
backbone
moment
async
')
- Clientside code as Node-style modules
- Now that we have a common pattern for code we right and modules we use, they can all be required the same way.
- The only difference is we use relative paths for our modules
- Browserify does the rest of the magic using npm's logic for lookups within node_modules dir
- We now have reusable components for every part of our appA Client Page
// pages/saveShow.js
var Page = require('./base'); // Our base page module
var Show = require('../models/show'); // Our show module
var async = require('async'); // async, from npm
// Exporting our saveShow page module
module.exports = Page.extend({
initialize: function () {
this.model = new Show();
},
render: function () { async.each( /* ...stuff... */ ); }
});
- We include a base component
- and a model
- and async from npm
- and we extend our base module and add functionalityContinuous Deployments
A commit is a deployA branch is an environment
- Someone will know somewhere when a deployment doesn't work
- Code is real once its running on someone else's machineContinuous Deployments
"Works on my machine"
Nobody
- Nobody is committing code and not knowing if it works when deployedAll JS
- Node API, Node App
- Developer productivity++
- Devs can add features top to bottom
- Developers can jump back and forth between the two with minimal context switching
- I was able to hop from the app to the API to implement pusher support from the API down to the app
- Even though we had separate teams for the app and API it is still so great to be able to add a feature to the API and app when necessary
- As you can see I was able to commit 21 times to the API, even though thats not a huge amount, I was still able to fix some bugs and add features during my normal workflow which was highly valuable to the team as a wholeProd Environment
-
Minifies by default
-
Caches for 1 year
- Filename hashed based on contents
-
GET /app.
fd504f36
647efd62
.js
200
(from cache)
200
(from cache)
- Application code is on the device
- Minifies and gzips out code from 1.8MB to 218KB
- We cache for a full year with the confidence that when we deploy, if the hash of the file changes, the filename will change.
- It's not even doing an HTTP request. It just executes the code.
- So the user has almost all of the application code stored on the device and is just requesting and responding to API data.
- The end result is no request after the first one and no latency for the bulk of the application codeEveryone is happy
- Developers love the process
- Users love the speed
- Stakeholders love the timeline
- As a developer im able to develop from dev to production easily and focus on the harder issues
- Users have a fast app on all platforms
- Stakeholders get rapid iteration and something great that is built quickly