Your SlideShare is downloading. ×
0
Building a Single-Page App:             Backbone, Node.js, and Beyond                             Spike Brehm, Front End E...
Past: Why Single-Page Apps                        Present: How we built Wish Lists                        Future: In pursu...
Past                             Why Single-Page AppsThursday, September 13, 12
Thursday, September 13, 12
Airbedandbreakfast.comThursday, September 13, 12
Airbedandbreakfast.com                       • Started in 2008 as a Rails 2.x appThursday, September 13, 12
Airbedandbreakfast.com                       • Started in 2008 as a Rails 2.x app                       • Now Rails 3.0Thu...
Airbedandbreakfast.com                       • Started in 2008 as a Rails 2.x app                       • Now Rails 3.0   ...
What is a Single-Page App?Thursday, September 13, 12
What is a Single-Page App?Thursday, September 13, 12
What is a Single-Page App?Thursday, September 13, 12
What is a Single-Page App?                  • Navigate in the app without page refreshThursday, September 13, 12
What is a Single-Page App?                  • Navigate in the app without page refresh                  • Application logi...
What is a Single-Page App?                  • Navigate in the app without page refresh                  • Application logi...
Why Single-Page Apps?Thursday, September 13, 12
Why Single-Page Apps?                        • Faster JavaScript runtimesThursday, September 13, 12
Why Single-Page Apps?                        • Faster JavaScript runtimes                        • New browser features (p...
Why Single-Page Apps?                        • Faster JavaScript runtimes                        • New browser features (p...
Two Approaches                              The Easy Way                              The Hard Way                        ...
The Easy WayThursday, September 13, 12
The Easy WayThursday, September 13, 12
The Easy Way                             •   JavaScript app runs entirely in clientThursday, September 13, 12
The Easy Way                             •   JavaScript app runs entirely in client                             •   Server...
The Easy Way                             •   JavaScript app runs entirely in client                             •   Server...
The Easy Way                             •   JavaScript app runs entirely in client                             •   Server...
The Easy Way                             •   JavaScript app runs entirely in client                             •   Server...
The Easy Way                             •   JavaScript app runs entirely in client                             •   Server...
The Hard Way             aka “The Holy Grail”Thursday, September 13, 12
The Hard WayThursday, September 13, 12
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
The Hard Way                             •   Routing, templating, application logic, utilities run on                     ...
Performance                    Improving performance on Twitter.com                    http://engineering.twitter.com/2012...
Stops and StartsThursday, September 13, 12
Stops and Starts                        •    mustache.rb: code duplicationThursday, September 13, 12
Stops and Starts                        •    mustache.rb: code duplication                        •    therubyracer: perfo...
Stops and Starts                        •    mustache.rb: code duplication                        •    therubyracer: perfo...
Present                             How we built Wish ListsThursday, September 13, 12
Thursday, September 13, 12
TechnologiesThursday, September 13, 12
Technologies                  • MV*: Backbone.jsThursday, September 13, 12
Technologies                  • MV*: Backbone.js                  • Templating: HandlebarsThursday, September 13, 12
Technologies                  • MV*: Backbone.js                  • Templating: Handlebars                  • UI & Layout:...
Technologies                  • MV*: Backbone.js                  • Templating: Handlebars                  • UI & Layout:...
Technologies                  • MV*: Backbone.js                  • Templating: Handlebars                  • UI & Layout:...
Technologies                  • MV*: Backbone.js                  • Templating: Handlebars                  • UI & Layout:...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Rails-Backbone interface:                       index.html.erb <div class=”app_view”></div> <script> !function(){   I18n.e...
Bootstrapping the app                   window.WishlistsApp = new AIR.Apps.Wishlists({                     “listings”: [.....
Bootstrapping the app                   window.WishlistsApp = new AIR.Apps.Wishlists({                     “listings”: [.....
Bootstrapping the appThursday, September 13, 12
Bootstrapping the app                    • Each action bootstraps whatever data                             needed on first...
Bootstrapping the app                    • Each action bootstraps whatever data                             needed on first...
App Initialize class AIR.Apps.Wishlists extends Backbone.Model      initialize: =>        @wishlists = new AIR.Collections...
App Initialize class AIR.Apps.Wishlists extends Backbone.Model      initialize: =>        @wishlists = new AIR.Collections...
Backbone RouterThursday, September 13, 12
Backbone Router                    • Translates URL changes to method                             callsThursday, September...
Backbone Router                    • Translates URL changes to method                             calls                   ...
Backbone Router                    • Translates URL changes to method                             calls                   ...
Backbone Router                    • Translates URL changes to method                             calls                   ...
Backbone Router    class AIR.Routers.Wishlists extends Backbone.Router      routes:        wishlists/:id            : show...
Backbone Router    class AIR.Routers.Wishlists extends Backbone.Router      routes:        wishlists/:id            : show...
Backbone Router    class AIR.Routers.Wishlists extends Backbone.Router      routes:        wishlists/:id            : show...
Backbone Router    class AIR.Routers.Wishlists extends Backbone.Router      routes:        wishlists/:id            : show...
Data-on-demandThursday, September 13, 12
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
Data-on-demand                             # AIR.Apps.Wishlists                             fetchWishlist: (id, callback) ...
api.airbnb.comThursday, September 13, 12
api.airbnb.com                             •   Used by iOS, Android, Mobile Web clientsThursday, September 13, 12
api.airbnb.com                             •   Used by iOS, Android, Mobile Web clients                             •   No...
api.airbnb.com                             •   Used by iOS, Android, Mobile Web clients                             •   No...
api.airbnb.com                             •   Used by iOS, Android, Mobile Web clients                             •   No...
Accessing API from                           Backbone      Airbnb.Api.getUrl(‘/v1/users/1234’)Thursday, September 13, 12
Accessing API from                           Backbone      Airbnb.Api.getUrl(‘/v1/users/1234’)      => "https://api.airbnb...
Accessing API from                           Backbone    class AIR.Models.WishlistUser extends Backbone.Model      jsonKey...
Accessing API from                           Backbone        AIR.Mixins.ApiResource =          url: (options = {}) ->     ...
Accessing API from                           Backbone        AIR.Mixins.ApiResource =          url: (options = {}) ->     ...
Accessing API from                           Backbone        AIR.Mixins.ApiResource =          url: (options = {}) ->     ...
Accessing API from                           Backbone        AIR.Mixins.ApiResource =          url: (options = {}) ->     ...
AIR.Views.BaseView           class AIR.Views.BaseView extends Backbone.View                 postInitialize: ->            ...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
Before      class WishlistIndexView extends Backbone.View           template: wishlists/wishlist_index_view           rend...
After      class WishlistIndexView extends AIR.Views.BaseView           template: wishlists/wishlist_index_view           ...
After      class WishlistIndexView extends AIR.Views.BaseView           template: wishlists/wishlist_index_view           ...
After      class WishlistIndexView extends AIR.Views.BaseView           template: wishlists/wishlist_index_view           ...
Before, Part II      class WishlistIndexView extends Backbone.View           ...           render: ->                @$el....
Before, Part II      class WishlistIndexView extends Backbone.View           ...           render: ->                data ...
Before, Part II      class WishlistIndexView extends Backbone.View           ...           render: ->                @$el....
After, Part II      class WishlistIndexView extends AIR.Views.BaseView           ...           getRenderData: ->          ...
After, Part II      class WishlistIndexView extends AIR.Views.BaseView           ...           getRenderData: ->          ...
cleanup()      class AIR.Views.BaseView extends Backbone.View           ...           cleanup: ->                @undelega...
cleanup()      class WishlistIndexView extends AIR.Views.BaseView           ...           cleanup: ->                super...
cleanup()            Backbone 0.9.2 adds new method:            Backbone.View.prototype.dispose()Thursday, September 13, 12
Modular, DRY viewsThursday, September 13, 12
Modular, DRY views                             • Re-usable bits of markup and                               behaviorThursd...
Modular, DRY views                             • (screenshot)Thursday, September 13, 12
Modular, DRY views                             • (screenshot)Thursday, September 13, 12
Subview initialization             class EditView extends AIR.Views.BaseView                 ...                 postRende...
Subview initialization             class EditView extends AIR.Views.BaseView                 ...                 postRende...
Subview initialization             class EditView extends AIR.Views.BaseView                 ...                 postRende...
Subview initialization             class EditView extends AIR.Views.BaseView                 ...                 postRende...
Subview initialization             class EditView extends AIR.Views.BaseView                 ...                 postRende...
What goes into a                                  view?Thursday, September 13, 12
What goes into a                                  view?•       app/assets/coffeescripts/views/shared/privacy_dropdown_view...
What goes into a                                  view?•       app/assets/coffeescripts/views/shared/privacy_dropdown_view...
What goes into a                                  view?•       app/assets/coffeescripts/views/shared/privacy_dropdown_view...
What goes into a                                  view?•       app/assets/coffeescripts/views/shared/privacy_dropdown_view...
Rdio’s Backbone-based View Component Framework                    Justin Tulloss, @justin_tulloss                    http:...
Modular, DRY views                             • (screenshot)Thursday, September 13, 12
http://airbnb.github.com/infinity                                    Infinity.js                             • (screenshot)T...
I18n.jsThursday, September 13, 12
I18n.js                             • 192 countriesThursday, September 13, 12
I18n.js                             • 192 countries                             • 31 localesThursday, September 13, 12
I18n.js                             • 192 countries                             • 31 locales                             •...
I18n.t()                   I18n.t(edit_wish_list);Thursday, September 13, 12
I18n.t()                   I18n.t(edit_wish_list);                   "Edit Wish List"Thursday, September 13, 12
I18n.t()                   I18n.t(edit_wish_list);                   "Edit Wish List"                   <h1>{{t "edit_wish...
I18n.t()                   I18n.t(edit_wish_list);                   "Edit Wish List"                   <h1>{{t "edit_wish...
Interpolation      I18n.t(owners_wish_list, {name: name});Thursday, September 13, 12
Interpolation      I18n.t(owners_wish_list, {name: name});      "Spike’s Wish List"Thursday, September 13, 12
Interpolation      I18n.t(owners_wish_list, {name: name});      "Spike’s Wish List"      <h1>{{t "owners_wish_list" name=n...
Interpolation      I18n.t(owners_wish_list, {name: name});      "Spike’s Wish List"      <h1>{{t "owners_wish_list" name=n...
I18n.extend()            I18n.extend({              "edit_wish_list": "Edit Wish List",              "owners_wish_list": "...
I18n.pluralize()    I18n.pluralize("Listing", listings);Thursday, September 13, 12
I18n.pluralize()    I18n.pluralize("Listing", listings);     3 ListingsThursday, September 13, 12
I18n.pluralize()    I18n.pluralize("Listing", listings);     3 Listings    <span>{{t_pluralize "Listing" count=listings}}<...
I18n.pluralize()    I18n.pluralize("Listing", listings);     3 Listings    <span>{{t_pluralize "Listing" count=listings}}<...
pluralize() just calls t()   {        "pluralize.Listing.zero": "%{count} Listings",        "pluralize.Listing.one": "%{co...
PhraseBundleThursday, September 13, 12
PhraseBundle                       • Composable bundles of I18n phrasesThursday, September 13, 12
PhraseBundle                       • Composable bundles of I18n phrases                       • Keep phrases DRYThursday, ...
PhraseBundle                       • Composable bundles of I18n phrases                       • Keep phrases DRY          ...
PhraseBundle   I18n.extend(<%= {     map_view => t(wishlists.Map View, :default => Map View),     list_view => t(wishlists...
PhraseBundle   I18n.extend(<%= {     map_view => t(wishlists.Map View, :default => Map View),     list_view => t(wishlists...
PhraseBundle module PhraseBundles   class Wishlists < PhraseBundle     includes :privacy_dropdown, :share_dropdown, :wishl...
PhraseBundle module PhraseBundles   class Wishlists < PhraseBundle     includes :privacy_dropdown, :share_dropdown, :wishl...
PhraseBundle module PhraseBundles   class Wishlists < PhraseBundle     includes :privacy_dropdown, :share_dropdown, :wishl...
CDN Asset URLs             • Image paths need to go through SprocketsThursday, September 13, 12
CDN Asset URLs             • Image paths need to go through Sprockets           Development:        https://localhost.airb...
CDN Asset URLs             • Image paths need to go through Sprockets           Development:        https://localhost.airb...
CDN Asset URLsThursday, September 13, 12
CDN Asset URLs         window.ImagePaths = <%= map_image_paths([           icons/facebook.png,           ...         ]).to...
CDN Asset URLs         window.ImagePaths = <%= map_image_paths([           icons/facebook.png,           ...         ]).to...
CDN Asset URLs         window.ImagePaths = <%= map_image_paths([           icons/facebook.png,           ...         ]).to...
Future                             In pursuit of the Holy GrailThursday, September 13, 12
Backbone.js is just a                                  stopgapThursday, September 13, 12
Backbone.js is just a                                  stopgap                       • Backbone.View is DOM-centricThursda...
Backbone.js is just a                                  stopgap                       • Backbone.View is DOM-centric       ...
Backbone.js is just a                                  stopgap                       • Backbone.View is DOM-centric       ...
It’s a great time to be a JavaScript hacker.Thursday, September 13, 12
It’s a great time to be a JavaScript hacker.                  But not a great time to build modern,                       ...
Testing the Node.js WatersThursday, September 13, 12
Testing the Node.js Waters        We are refactoring m.airbnb.com with a        Node backend instead of Rails.Thursday, Se...
Testing the Node.js Waters        We are refactoring m.airbnb.com with a        Node backend instead of Rails.        Prim...
Testing the Node.js Waters        We are refactoring m.airbnb.com with a        Node backend instead of Rails.        Prim...
Testing the Node.js WatersThursday, September 13, 12
Node FrameworksThursday, September 13, 12
Node Frameworks        Geddy, Tower        Rails-inspired. Not utilizing Node’s strengths.Thursday, September 13, 12
Node Frameworks        Geddy, Tower        Rails-inspired. Not utilizing Node’s strengths.        SocketStream        Modu...
Node Frameworks        Geddy, Tower        Rails-inspired. Not utilizing Node’s strengths.        SocketStream        Modu...
Node Frameworks        Geddy, Tower        Rails-inspired. Not utilizing Node’s strengths.        SocketStream        Modu...
Node Frameworks        Active authors.        Active mailing list.        Small, if messy, codebase.        Derby        S...
Node Frameworks        Derby        Active authors.        Active mailing list.        Small, if messy, codebase.        S...
Node Frameworks        Derby        Active authors.        Active mailing list.        Small, if messy, codebase.        S...
Other Resources                   Single Page App Book, by Mikito Takada                   http://singlepageappbook.com/  ...
res.end()Thursday, September 13, 12
Let’s chat                               @spikebrehmThursday, September 13, 12
Upcoming SlideShare
Loading in...5
×

Building a Single-Page App: Backbone, Node.js, and Beyond

24,115

Published on

Transcript of "Building a Single-Page App: Backbone, Node.js, and Beyond"

  1. 1. Building a Single-Page App: Backbone, Node.js, and Beyond Spike Brehm, Front End Engineer spike@airbnb.com @spikebrehm September 12, 2012Thursday, September 13, 12
  2. 2. Past: Why Single-Page Apps Present: How we built Wish Lists Future: In pursuit of the Holy GrailThursday, September 13, 12
  3. 3. Past Why Single-Page AppsThursday, September 13, 12
  4. 4. Thursday, September 13, 12
  5. 5. Airbedandbreakfast.comThursday, September 13, 12
  6. 6. Airbedandbreakfast.com • Started in 2008 as a Rails 2.x appThursday, September 13, 12
  7. 7. Airbedandbreakfast.com • Started in 2008 as a Rails 2.x app • Now Rails 3.0Thursday, September 13, 12
  8. 8. Airbedandbreakfast.com • Started in 2008 as a Rails 2.x app • Now Rails 3.0 • Still stuck in old, page-based paradigmThursday, September 13, 12
  9. 9. What is a Single-Page App?Thursday, September 13, 12
  10. 10. What is a Single-Page App?Thursday, September 13, 12
  11. 11. What is a Single-Page App?Thursday, September 13, 12
  12. 12. What is a Single-Page App? • Navigate in the app without page refreshThursday, September 13, 12
  13. 13. What is a Single-Page App? • Navigate in the app without page refresh • Application logic in the clientThursday, September 13, 12
  14. 14. What is a Single-Page App? • Navigate in the app without page refresh • Application logic in the client • Fetch data on demandThursday, September 13, 12
  15. 15. Why Single-Page Apps?Thursday, September 13, 12
  16. 16. Why Single-Page Apps? • Faster JavaScript runtimesThursday, September 13, 12
  17. 17. Why Single-Page Apps? • Faster JavaScript runtimes • New browser features (pushState, localStorage, etc.)Thursday, September 13, 12
  18. 18. Why Single-Page Apps? • Faster JavaScript runtimes • New browser features (pushState, localStorage, etc.) • Heightened user expectationsThursday, September 13, 12
  19. 19. Two Approaches The Easy Way The Hard Way aka “The Holy Grail”Thursday, September 13, 12
  20. 20. The Easy WayThursday, September 13, 12
  21. 21. The Easy WayThursday, September 13, 12
  22. 22. The Easy Way • JavaScript app runs entirely in clientThursday, September 13, 12
  23. 23. The Easy Way • JavaScript app runs entirely in client • Server technology agnosticThursday, September 13, 12
  24. 24. The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure appThursday, September 13, 12
  25. 25. The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlableThursday, September 13, 12
  26. 26. The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlable • Performance hit to download & evaluate JS before renderingThursday, September 13, 12
  27. 27. The Easy Way • JavaScript app runs entirely in client • Server technology agnostic • Can use Backbone to structure app • Poor SEO -- not crawlable • Performance hit to download & evaluate JS before rendering • Good for apps behind login, or toolsThursday, September 13, 12
  28. 28. The Hard Way aka “The Holy Grail”Thursday, September 13, 12
  29. 29. The Hard WayThursday, September 13, 12
  30. 30. The Hard Way • Routing, templating, application logic, utilities run on client and serverThursday, September 13, 12
  31. 31. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTMLThursday, September 13, 12
  32. 32. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation)Thursday, September 13, 12
  33. 33. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT)Thursday, September 13, 12
  34. 34. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fitThursday, September 13, 12
  35. 35. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fit • Provides good SEOThursday, September 13, 12
  36. 36. The Hard Way • Routing, templating, application logic, utilities run on client and server • Navigate to any page, HTML rendered in client -- hit refresh, serves up HTML • Must render full page of HTML without access to DOM (or find a faster DOM implementation) • Requires JavaScript runtime on the server (or DSL that compiles down to JavaScript -- think GWT) • Backbone not a good fit • Provides good SEO • Better performanceThursday, September 13, 12
  37. 37. Performance Improving performance on Twitter.com http://engineering.twitter.com/2012/05/improving-performance-on- twittercom.html “Time to first tweet”Thursday, September 13, 12
  38. 38. Stops and StartsThursday, September 13, 12
  39. 39. Stops and Starts • mustache.rb: code duplicationThursday, September 13, 12
  40. 40. Stops and Starts • mustache.rb: code duplication • therubyracer: performance, stabilityThursday, September 13, 12
  41. 41. Stops and Starts • mustache.rb: code duplication • therubyracer: performance, stability • PhantomJS: slow, overly complicatedThursday, September 13, 12
  42. 42. Present How we built Wish ListsThursday, September 13, 12
  43. 43. Thursday, September 13, 12
  44. 44. TechnologiesThursday, September 13, 12
  45. 45. Technologies • MV*: Backbone.jsThursday, September 13, 12
  46. 46. Technologies • MV*: Backbone.js • Templating: HandlebarsThursday, September 13, 12
  47. 47. Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap)Thursday, September 13, 12
  48. 48. Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScriptThursday, September 13, 12
  49. 49. Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScript • HTML5 pushStateThursday, September 13, 12
  50. 50. Technologies • MV*: Backbone.js • Templating: Handlebars • UI & Layout: Oxygen (Airbnb’s Bootstrap) • CoffeeScript • HTML5 pushState • api.airbnb.comThursday, September 13, 12
  51. 51. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  52. 52. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  53. 53. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  54. 54. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  55. 55. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  56. 56. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  57. 57. Rails-Backbone interface: index.html.erb <div class=”app_view”></div> <script> !function(){ I18n.extend(<%= @phrases.to_json.html_safe %>); Airbnb.Api.config(<%= @api_config.to_json.html_safe %>); window.WishlistsApp = new AIR.Apps.Wishlists( <%= @init_data.to_json.html_safe %> ); }(); </script>Thursday, September 13, 12
  58. 58. Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... });Thursday, September 13, 12
  59. 59. Bootstrapping the app window.WishlistsApp = new AIR.Apps.Wishlists({ “listings”: [...], “wishlists”: [...], ... }); WishlistsApp.get(‘wishlists’) => [Object, Object, Object, ...]Thursday, September 13, 12
  60. 60. Bootstrapping the appThursday, September 13, 12
  61. 61. Bootstrapping the app • Each action bootstraps whatever data needed on first pageloadThursday, September 13, 12
  62. 62. Bootstrapping the app • Each action bootstraps whatever data needed on first pageload • Subsequent data is requested on- demandThursday, September 13, 12
  63. 63. App Initialize class AIR.Apps.Wishlists extends Backbone.Model initialize: => @wishlists = new AIR.Collections.Wishlists @get(wishlists) @listings = new AIR.Collections.Listings @get(listings) ... new AIR.Routers.Wishlists({app: @})Thursday, September 13, 12
  64. 64. App Initialize class AIR.Apps.Wishlists extends Backbone.Model initialize: => @wishlists = new AIR.Collections.Wishlists @get(wishlists) @listings = new AIR.Collections.Listings @get(listings) ... new AIR.Routers.Wishlists({app: @}) WishlistsApp.wishlists => Wishlists _byCid: Object _byId: Object length: 11 models: Array[11] __proto__: ctorThursday, September 13, 12
  65. 65. Backbone RouterThursday, September 13, 12
  66. 66. Backbone Router • Translates URL changes to method callsThursday, September 13, 12
  67. 67. Backbone Router • Translates URL changes to method calls • Source of global app stateThursday, September 13, 12
  68. 68. Backbone Router • Translates URL changes to method calls • Source of global app state • Keep state out of viewsThursday, September 13, 12
  69. 69. Backbone Router • Translates URL changes to method calls • Source of global app state • Keep state out of views • Idempotent view renderingThursday, September 13, 12
  70. 70. Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
  71. 71. Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
  72. 72. Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
  73. 73. Backbone Router class AIR.Routers.Wishlists extends Backbone.Router routes: wishlists/:id : show wishlists/:id/edit : edit ... show: (id) -> @app.fetchWishlist id, (model) => view = new AIR.Views.Wishlists.ShowView {@app, model} @updateContent(view)Thursday, September 13, 12
  74. 74. Data-on-demandThursday, September 13, 12
  75. 75. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  76. 76. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  77. 77. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  78. 78. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  79. 79. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  80. 80. Data-on-demand # AIR.Apps.Wishlists fetchWishlist: (id, callback) -> model = @wishlists.get(id) if model? callback(model) else @appView.setLoading(true) @wishlists.fetchById id, (model) => @appView.setLoading(false) callback(model)Thursday, September 13, 12
  81. 81. api.airbnb.comThursday, September 13, 12
  82. 82. api.airbnb.com • Used by iOS, Android, Mobile Web clientsThursday, September 13, 12
  83. 83. api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHRThursday, September 13, 12
  84. 84. api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHR • JSONP for GET; but no POST, PUT, DELETEThursday, September 13, 12
  85. 85. api.airbnb.com • Used by iOS, Android, Mobile Web clients • No Cross-Domain XHR • JSONP for GET; but no POST, PUT, DELETE • Added CORS support in API to allow requests coming from valid Airbnb domain (*.airbnb.com, *.airbnb.co.uk, *.airbnb.de...)Thursday, September 13, 12
  86. 86. Accessing API from Backbone Airbnb.Api.getUrl(‘/v1/users/1234’)Thursday, September 13, 12
  87. 87. Accessing API from Backbone Airbnb.Api.getUrl(‘/v1/users/1234’) => "https://api.airbnb.com/v1/users/1234?currency=USD&locale=en& key=...&oauth_token=..."Thursday, September 13, 12
  88. 88. Accessing API from Backbone class AIR.Models.WishlistUser extends Backbone.Model jsonKey: user apiPath: -> "/v1/users/#{@id}" ... _.extend AIR.Models.WishlistUser.prototype, AIR.Mixins.ApiResourceThursday, September 13, 12
  89. 89. Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
  90. 90. Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
  91. 91. Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
  92. 92. Accessing API from Backbone AIR.Mixins.ApiResource = url: (options = {}) -> apiPath = options.apiPath || @apiPath if _.isFunction(apiPath) apiPath = apiPath.call(@) Airbnb.Api.getUrl(apiPath) sync: (method, model, options) -> options = _.defaults options, url: @url(options) Backbone.sync method, model, optionsThursday, September 13, 12
  93. 93. AIR.Views.BaseView class AIR.Views.BaseView extends Backbone.View postInitialize: -> postRender: -> getRenderData: -> cleanup: -> ...Thursday, September 13, 12
  94. 94. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  95. 95. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  96. 96. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  97. 97. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  98. 98. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  99. 99. Before class WishlistIndexView extends Backbone.View template: wishlists/wishlist_index_view render: -> @$el.html JST[@template](@model.toJSON()) @renderSomeThing() @ renderSomeThing: -> ...Thursday, September 13, 12
  100. 100. After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
  101. 101. After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
  102. 102. After class WishlistIndexView extends AIR.Views.BaseView template: wishlists/wishlist_index_view postRender: -> @renderSomeThing() renderSomeThing: -> ...Thursday, September 13, 12
  103. 103. Before, Part II class WishlistIndexView extends Backbone.View ... render: -> @$el.html JST[@template](@model.toJSON()) @Thursday, September 13, 12
  104. 104. Before, Part II class WishlistIndexView extends Backbone.View ... render: -> data = _.extend @model.toJSON(), show_share_button: @options.show_share_button @$el.html JST[@template](data) @Thursday, September 13, 12
  105. 105. Before, Part II class WishlistIndexView extends Backbone.View ... render: -> @$el.html JST[@template](@getRenderData()) @ getRenderData: -> _.extend @model.toJSON(), show_share_button: @options.show_share_buttonThursday, September 13, 12
  106. 106. After, Part II class WishlistIndexView extends AIR.Views.BaseView ... getRenderData: -> _.extend super, show_share_button: @options.show_share_buttonThursday, September 13, 12
  107. 107. After, Part II class WishlistIndexView extends AIR.Views.BaseView ... getRenderData: -> _.extend super, show_share_button: @options.show_share_buttonThursday, September 13, 12
  108. 108. cleanup() class AIR.Views.BaseView extends Backbone.View ... cleanup: -> @undelegateEvents() @model?.off(null, null, @) @remove()Thursday, September 13, 12
  109. 109. cleanup() class WishlistIndexView extends AIR.Views.BaseView ... cleanup: -> super @someChildView.cleanup() clearInterval(@interval)Thursday, September 13, 12
  110. 110. cleanup() Backbone 0.9.2 adds new method: Backbone.View.prototype.dispose()Thursday, September 13, 12
  111. 111. Modular, DRY viewsThursday, September 13, 12
  112. 112. Modular, DRY views • Re-usable bits of markup and behaviorThursday, September 13, 12
  113. 113. Modular, DRY views • (screenshot)Thursday, September 13, 12
  114. 114. Modular, DRY views • (screenshot)Thursday, September 13, 12
  115. 115. Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
  116. 116. Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
  117. 117. Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
  118. 118. Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
  119. 119. Subview initialization class EditView extends AIR.Views.BaseView ... postRender: -> @renderPrivacyDropdown() renderPrivacyDropdown: -> view = new AIR.Views.Shared.PrivacyDropdownView private: @model.get(private) view.on ‘private-changed’, (isPrivate) => # do something console.log(isPrivate) @$(data-privacy-dropdown).replaceWith view.render().elThursday, September 13, 12
  120. 120. What goes into a view?Thursday, September 13, 12
  121. 121. What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffeeThursday, September 13, 12
  122. 122. What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbsThursday, September 13, 12
  123. 123. What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbs• app/assets/stylesheets/partials/_ privacy_dropdown_view.scssThursday, September 13, 12
  124. 124. What goes into a view?• app/assets/coffeescripts/views/shared/privacy_dropdown_view.coffee• app/assets/templates/views/shared/privacy_dropdown_view.hbs• app/assets/stylesheets/partials/_ privacy_dropdown_view.scss• lib/phrase_bundles/privacy_dropdown_view.rbThursday, September 13, 12
  125. 125. Rdio’s Backbone-based View Component Framework Justin Tulloss, @justin_tulloss http://www.youtube.com/watch?v=TB-l2nF67iUThursday, September 13, 12
  126. 126. Modular, DRY views • (screenshot)Thursday, September 13, 12
  127. 127. http://airbnb.github.com/infinity Infinity.js • (screenshot)Thursday, September 13, 12
  128. 128. I18n.jsThursday, September 13, 12
  129. 129. I18n.js • 192 countriesThursday, September 13, 12
  130. 130. I18n.js • 192 countries • 31 localesThursday, September 13, 12
  131. 131. I18n.js • 192 countries • 31 locales • Client-slide translation libraryThursday, September 13, 12
  132. 132. I18n.t() I18n.t(edit_wish_list);Thursday, September 13, 12
  133. 133. I18n.t() I18n.t(edit_wish_list); "Edit Wish List"Thursday, September 13, 12
  134. 134. I18n.t() I18n.t(edit_wish_list); "Edit Wish List" <h1>{{t "edit_wish_list"}}</h1>Thursday, September 13, 12
  135. 135. I18n.t() I18n.t(edit_wish_list); "Edit Wish List" <h1>{{t "edit_wish_list"}}</h1> <h1>Edit Wish List</h1>Thursday, September 13, 12
  136. 136. Interpolation I18n.t(owners_wish_list, {name: name});Thursday, September 13, 12
  137. 137. Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List"Thursday, September 13, 12
  138. 138. Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List" <h1>{{t "owners_wish_list" name=name}}</h1>Thursday, September 13, 12
  139. 139. Interpolation I18n.t(owners_wish_list, {name: name}); "Spike’s Wish List" <h1>{{t "owners_wish_list" name=name}}</h1> <h1>Spike’s Wish List</h1>Thursday, September 13, 12
  140. 140. I18n.extend() I18n.extend({ "edit_wish_list": "Edit Wish List", "owners_wish_list": "%{name}’s Wish List", ... });Thursday, September 13, 12
  141. 141. I18n.pluralize() I18n.pluralize("Listing", listings);Thursday, September 13, 12
  142. 142. I18n.pluralize() I18n.pluralize("Listing", listings); 3 ListingsThursday, September 13, 12
  143. 143. I18n.pluralize() I18n.pluralize("Listing", listings); 3 Listings <span>{{t_pluralize "Listing" count=listings}}</span>Thursday, September 13, 12
  144. 144. I18n.pluralize() I18n.pluralize("Listing", listings); 3 Listings <span>{{t_pluralize "Listing" count=listings}}</span> <span>3 Listings</span>Thursday, September 13, 12
  145. 145. pluralize() just calls t() { "pluralize.Listing.zero": "%{count} Listings", "pluralize.Listing.one": "%{count} Listing", "pluralize.Listing.many": "%{count} Listings" }Thursday, September 13, 12
  146. 146. PhraseBundleThursday, September 13, 12
  147. 147. PhraseBundle • Composable bundles of I18n phrasesThursday, September 13, 12
  148. 148. PhraseBundle • Composable bundles of I18n phrases • Keep phrases DRYThursday, September 13, 12
  149. 149. PhraseBundle • Composable bundles of I18n phrases • Keep phrases DRY • Separation of concerns: treat phrases as data sourceThursday, September 13, 12
  150. 150. PhraseBundle I18n.extend(<%= { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... }.to_json.html_safe %>);Thursday, September 13, 12
  151. 151. PhraseBundle I18n.extend(<%= { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... }.to_json.html_safe %>); I18n.extend(<%= PhraseBundles::Wishlists.new.to_json.html_safe %>);Thursday, September 13, 12
  152. 152. PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
  153. 153. PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
  154. 154. PhraseBundle module PhraseBundles class Wishlists < PhraseBundle includes :privacy_dropdown, :share_dropdown, :wishlists_modal def phrases { map_view => t(wishlists.Map View, :default => Map View), list_view => t(wishlists.List View, :default => List View), ... } end end endThursday, September 13, 12
  155. 155. CDN Asset URLs • Image paths need to go through SprocketsThursday, September 13, 12
  156. 156. CDN Asset URLs • Image paths need to go through Sprockets Development: https://localhost.airbnb.com:3001 /static/icons/facebook.pngThursday, September 13, 12
  157. 157. CDN Asset URLs • Image paths need to go through Sprockets Development: https://localhost.airbnb.com:3001 /static/icons/facebook.png https://a0.muscache.com/airbnb Production: /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.pngThursday, September 13, 12
  158. 158. CDN Asset URLsThursday, September 13, 12
  159. 159. CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>;Thursday, September 13, 12
  160. 160. CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>; ImagePaths[icons/facebook.png]; => “https://a0.muscache.com/airbnb /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.png”Thursday, September 13, 12
  161. 161. CDN Asset URLs window.ImagePaths = <%= map_image_paths([ icons/facebook.png, ... ]).to_json.html_safe %>; ImagePaths[icons/facebook.png]; => “https://a0.muscache.com/airbnb /static/icons/facebook- e04e8c0c43e40ff7a277a3a7a734ed52.png” <img src=”{{image_path “icons/facebook.png”}}” ...>Thursday, September 13, 12
  162. 162. Future In pursuit of the Holy GrailThursday, September 13, 12
  163. 163. Backbone.js is just a stopgapThursday, September 13, 12
  164. 164. Backbone.js is just a stopgap • Backbone.View is DOM-centricThursday, September 13, 12
  165. 165. Backbone.js is just a stopgap • Backbone.View is DOM-centric • Backbone.History is window-centricThursday, September 13, 12
  166. 166. Backbone.js is just a stopgap • Backbone.View is DOM-centric • Backbone.History is window-centric • Backbone.Model and Backbone.Collection are more portable (with override of Backbone.sync)Thursday, September 13, 12
  167. 167. It’s a great time to be a JavaScript hacker.Thursday, September 13, 12
  168. 168. It’s a great time to be a JavaScript hacker. But not a great time to build modern, plug-and-play web apps.Thursday, September 13, 12
  169. 169. Testing the Node.js WatersThursday, September 13, 12
  170. 170. Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails.Thursday, September 13, 12
  171. 171. Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails. Primary goal is to learn how to productionize a Node app.Thursday, September 13, 12
  172. 172. Testing the Node.js Waters We are refactoring m.airbnb.com with a Node backend instead of Rails. Primary goal is to learn how to productionize a Node app. Secondary goal is to prototype a new way of building web apps.Thursday, September 13, 12
  173. 173. Testing the Node.js WatersThursday, September 13, 12
  174. 174. Node FrameworksThursday, September 13, 12
  175. 175. Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths.Thursday, September 13, 12
  176. 176. Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way.Thursday, September 13, 12
  177. 177. Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way. Meteor Solves for The Hard Way, but all-or-nothing. Alpha.Thursday, September 13, 12
  178. 178. Node Frameworks Geddy, Tower Rails-inspired. Not utilizing Node’s strengths. SocketStream Modular, real-time, but optimized for The Easy Way. Meteor Solves for The Hard Way, but all-or-nothing. Alpha. Derby Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
  179. 179. Node Frameworks Active authors. Active mailing list. Small, if messy, codebase. Derby Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
  180. 180. Node Frameworks Derby Active authors. Active mailing list. Small, if messy, codebase. Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
  181. 181. Node Frameworks Derby Active authors. Active mailing list. Small, if messy, codebase. Solves for The Hard Way, but not very modular. Alpha.Thursday, September 13, 12
  182. 182. Other Resources Single Page App Book, by Mikito Takada http://singlepageappbook.com/ view.json, by Mikito Takada http://mixu.net/view.json/ Building The Next SoundCloud http://backstage.soundcloud.com/2012/06/building-the-next- soundcloud/ Sean McBride, Bridging the Client-Server Divide http://seanmcb.com/client-server-divide/ NodeUp Podcast http://nodeup.com/Thursday, September 13, 12
  183. 183. res.end()Thursday, September 13, 12
  184. 184. Let’s chat @spikebrehmThursday, September 13, 12
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×