Introducing Rendr: Run your Backbone.js apps on the client and server

68,499 views

Published on

Published in: Technology, Business
3 Comments
44 Likes
Statistics
Notes
No Downloads
Views
Total views
68,499
On SlideShare
0
From Embeds
0
Number of Embeds
49,597
Actions
Shares
0
Downloads
151
Comments
3
Likes
44
Embeds 0
No embeds

No notes for slide

Introducing Rendr: Run your Backbone.js apps on the client and server

  1. Introducing Rendr:Run your Backbone.js appson the client and serverSpike Brehm@spikebrehmHTML5DevConfApril 1, 2013
  2. 2008
  3. 2013
  4. Exciting times in theworld of web apps.<your framework here>
  5. Client-sideMVC
  6. +
  7. Poor SEO; not crawlablePerformance hit to downloadand parse JSDuplicated application logicContext switching
  8. It’s still a PITA to buildfast, maintainablerich-client apps.
  9. We started thinking...What if our JavaScriptapp could run on bothsides?
  10. Client +serverMVCaka “The Holy Grail”
  11. Provides SEOInitial pageload is drasticallyfasterConsolidated application logic
  12. Has anyone alreadydone this?
  13. Meteor: client/server, but noserver-side rendering; owns datalayerDerby: client+server rendering,but owns data layerMojito: client+server rendering,but full stack, and... YUI.
  14. Okay... how hard canit be?
  15. +
  16. +
  17. Rendr.
  18. What it is.
  19. What it is.JavaScript MVC on client & serverBackbone & HandlebarsBaseView, BaseModel,BaseCollection, BaseApp,ClientRouter, ServerRouter...Express middlewareMinimal glue between client & server
  20. What it ain’t.
  21. What it ain’t.Batteries-included web frameworkFinished
  22. Design goals:• Write application logic agnostic to environment.• Library, not a framework.• Minimize if (server) {...} else {...}.• Hide complexity in library.• Talk to RESTful API.• No server-side DOM.• Simple Express middleware.
  23. Classes:- BaseApp < Backbone.Model- BaseModel < Backbone.Model- BaseCollection < Backbone.Collection- BaseView < Backbone.View- AppView < BaseView- ClientRouter < BaseRouter- ServerRouter < BaseRouter- ModelStore < MemoryStore- CollectionStore < MemoryStore- Fetcher
  24. Rendr directory structure|- client/|- shared/ } Sent to client|- server/
  25. App directory structure|- app/|- public/|- server/
  26. App directory structure }|- app/|--- collections/|--- controllers/|--- models/ Entire app dir|--- templates/ gets sent to|--- views/|--- app.js client|--- router.js|--- routes.js|- public/|- server/
  27. CommonJS using StitchOn the server:var User = require(‘app/models/user’);var BaseView = require(‘rendr/shared/base/view’);On the client:var User = require(‘app/models/user’);var BaseView = require(‘rendr/shared/base/view’);
  28. app/routes.js module.exports = function(match) { match(, home#index); match(users, users#index); match(users/:id, users#show); };
  29. app/routes.js module.exports = function(match) { match(, home#index); match(users, users#index); match(users/:id, users#show); }; controller
  30. app/routes.js module.exports = function(match) { match(, home#index); match(users, users#index); match(users/:id, users#show); }; action
  31. Render lifecycle,server.
  32. • On server startup, parse routes file and mount as Express routes• GET /users/1337• Router matches "/users/:id" to "users#show" with params = {"id": 1337}• Router finds controller: require("app/controllers/users_controller")• Router executes show action with params = {"id": 1337}• The show action says to fetch User#1337 and use UsersShowView view class• Router instantiates new UsersShowView with data• Router calls view.getHtml()• Hands HTML to Express, which decorates with layout and serves response
  33. Render lifecycle,client.
  34. • On page load, Router parses routes file and mounts Backbone routes• pushState /users/1337• Router matches "/users/:id" to "users#show" with params = {"id": 1337}• Router finds controller: require("app/controllers/users_controller")• Router executes show action with params = {"id": 1337}• The show action says to fetch User#1337 and use UsersShowView view class• Router instantiates new UsersShowView with data• Router calls view.render()• Insert into DOM
  35. • On page load, Router parses routes file and mounts Backbone routes• pushState /users/1337• Router matches "/users/:id" to "users#show" with params = {"id": 1337}• Router finds controller: require("app/controllers/users_controller")• Router executes show action with params = {"id": 1337}• The show action says to fetch User#1337 and use UsersShowView view class• Router instantiates new UsersShowView with data• Router calls view.render()• Insert into DOM
  36. • On page load, Router parses routes file and mounts Backbone routes• pushState /users/1337• Router matches "/users/:id" to "users#show" with params = {"id": 1337}• Router finds controller: require("app/controllers/users_controller")• Router executes show action with params = {"id": 1337}• The show action says to fetch User#1337 and use UsersShowView view class• Router instantiates new UsersShowView with data• Router calls view.render()• Insert into DOM
  37. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};
  38. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};
  39. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};
  40. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};
  41. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};Most simple case: no fetching ofdata.
  42. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { callback(null, users_show_view); }};But we want to fetch the user.
  43. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, users_show_view, results); }); }};
  44. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, users_show_view, results); }); }};
  45. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, users_show_view, results); }); }};
  46. app/controllers/users_controller.jsmodule.exports = { show: function(params, callback) { var spec = { model: {model: ‘User’, params: params} }; this.app.fetch(spec, function(err, results) { callback(err, users_show_view, results); }); }};
  47. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view});module.exports.id = users_show_view;
  48. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view});module.exports.id = users_show_view;
  49. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view});module.exports.id = users_show_view;
  50. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view});module.exports.id = users_show_view;
  51. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view, events: { click p: handleClick }, handleClick: function() {...}});module.exports.id = users_show_view;
  52. app/templates/users_show_view.hbs<h1>User: {{name}}</h1><p>From {{city}}.</p>
  53. Rendered HTML<div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p></div>
  54. Rendered HTML<div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p></div>
  55. Rendered HTML<div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p></div>
  56. Rendered HTML<div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: Spike</h1> <p>From San Francisco.</p></div>
  57. Where’d the data come from?Where’s the render method?How do I customize what getspassed to the template?Sensible defaults.
  58. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view, getTemplateData: function() { }});
  59. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view, getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); }});
  60. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view, getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); // `data` is equivalent to this.model.toJSON() }});
  61. app/views/users_show_view.jsvar BaseView = require(rendr/shared/base/view);module.exports = BaseView.extend({ className: users_show_view, getTemplateData: function() { var data = BaseView.prototype.getTemplateData .call(this); // `data` is equivalent to this.model.toJSON() return _.extend(data, { nameUppercase: data.name.toUpperCase() }); }});
  62. app/templates/users_show_view.hbs<h1>User: {{nameUppercase}}</h1><p>From {{city}}.</p>
  63. Rendered HTML<div class="users_show_view" data-...> <h1>User: SPIKE</h1> <p>From San Francisco.</p></div>
  64. Rendered HTML with layout<!doctype html><html lang="en"><head>...</head><body><div id="content"> <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: SPIKE</h1> <p>From San Francisco.</p> </div></div><script>(function() {var App = window.App = new (require(app/app));App.bootstrapData({ "model":{"summary":{"model":"user","id":1337}, "data":{"name":"Spike","city":"San Francisco", ...}});App.start();})();</script></body></html>
  65. Rendered HTML with layout<!doctype html><html lang="en"><head>...</head><body><div id="content"> <div class="users_show_view" data-view="users_show_view" data-model_name="user" data-model_id="1337"> <h1>User: SPIKE</h1> <p>From San Francisco.</p> </div></div><script>(function() {var App = window.App = new (require(app/app));App.bootstrapData({ "model":{"summary":{"model":"user","id":"wycats"}, "data":{"name":"Spike","city":"San Francisco", ...}});App.start();})();</script></body></html>
  66. View hydration.
  67. 1. Find DOM els with data-view attribute.var viewEls = $("[data-view]");
  68. 2. Determine model or collection based ondata-model_name, data-model_id, etc.var modelName = viewEl.data(‘model_name’), modelId = viewEl.data(‘model_id’);console.log(modelName, modelId); => “user” 1337
  69. 3. Fetch model/collection data fromModelStore/CollectionStore.var model = modelStore.get(modelName, modelId);
  70. 4. Find view class.var viewName, ViewClass;viewName = viewEl.data(‘view’);ViewClass = require(app/views/ + viewName);
  71. 5. Instantiate view instance with model.var view = new ViewClass({ model: model, ...});
  72. 6. Attach to DOM el.view.setElement(viewEl);
  73. 7. Delegate events.view.delegateEvents();
  74. 8. Profit!alert(“That wasn’t so hard, right?”)
  75. Rendr is availablestarting today.github.com/airbnb/rendr
  76. TODO• Share routing logic between client & server.• Lazy load views, templates, etc as needed.• Support other templating languages.• Break down into smaller modules.• Rewrite in vanilla JavaScript.• much more...
  77. Hackerswanted.
  78. Thanks!@spikebrehm @rendrjs@AirbnbNerds

×