Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pitfalls and opportuniities of single-page applications

Pitfalls and opportuniities of single-page applications

Slides for my "Pitfalls" talk (jQuery UK, MMT29). See also https://github.com/jzaefferer/pitfalls-examples

8ba7af140cf671c5cab61154d5ee19ef?s=128

jzaefferer

March 28, 2012
Tweet

More Decks by jzaefferer

Other Decks in Technology

Transcript

  1. Pitfalls and opportunities of single-page applications Wednesday, March 28, 2012

  2. Yours truly Wednesday, March 28, 2012

  3. Yours truly Jörn Zaefferer <- that’s me Wednesday, March 28,

    2012
  4. Yours truly Jörn Zaefferer <- that’s me Writing a lot

    of JavaScript since 2006 Wednesday, March 28, 2012
  5. Yours truly Jörn Zaefferer <- that’s me Writing a lot

    of JavaScript since 2006 jQuery Core, jQuery UI, QUnit, jQuery Validation Plugin Wednesday, March 28, 2012
  6. Yours truly Jörn Zaefferer <- that’s me Writing a lot

    of JavaScript since 2006 jQuery Core, jQuery UI, QUnit, jQuery Validation Plugin Even more since December 2010 Wednesday, March 28, 2012
  7. Yours truly Jörn Zaefferer <- that’s me Writing a lot

    of JavaScript since 2006 jQuery Core, jQuery UI, QUnit, jQuery Validation Plugin Even more since December 2010 Main SPA experience: SoundCloud Mobile in 2011 Wednesday, March 28, 2012
  8. “A single-page application (SPA) is a web application or web

    site that fits on a single web page with the goal of providing a more fluid user experience akin to a desktop application.” --Wikipedia, Single-page application Wednesday, March 28, 2012
  9. Single Page Applications Wednesday, March 28, 2012

  10. Single Page Applications Static files and JSON API Wednesday, March

    28, 2012
  11. Single Page Applications Static files and JSON API No page

    reloads Wednesday, March 28, 2012
  12. Single Page Applications Static files and JSON API No page

    reloads Backbone et al Wednesday, March 28, 2012
  13. Opportunities Wednesday, March 28, 2012

  14. Simple application architecture Opportunities Wednesday, March 28, 2012

  15. Simple application architecture Better user experience Opportunities Wednesday, March 28,

    2012
  16. Simple application architecture Better user experience Super duper frameworks Opportunities

    Wednesday, March 28, 2012
  17. Pitfalls? Wednesday, March 28, 2012

  18. Better user experience? Wednesday, March 28, 2012

  19. Classic page Wednesday, March 28, 2012

  20. Classic page Wednesday, March 28, 2012

  21. Classic page Wednesday, March 28, 2012

  22. Single Page Wednesday, March 28, 2012

  23. Single Page Wednesday, March 28, 2012

  24. Wednesday, March 28, 2012

  25. History API Wednesday, March 28, 2012

  26. Wednesday, March 28, 2012

  27. HTML5 history API Wednesday, March 28, 2012

  28. HTML5 history API history.pushState(..., fragment) Wednesday, March 28, 2012

  29. HTML5 history API history.pushState(..., fragment) history.replaceState(..., fragment) Wednesday, March 28,

    2012
  30. HTML5 history API history.pushState(..., fragment) history.replaceState(..., fragment) document.addEventListener("popstate", callback, false)

    Wednesday, March 28, 2012
  31. HTML5 history libraries Wednesday, March 28, 2012

  32. HTML5 history libraries history.js Wednesday, March 28, 2012

  33. HTML5 history libraries history.js Backbone.history.start({ pushState: true }) Wednesday, March

    28, 2012
  34. HTML5 history libraries history.js Backbone.history.start({ pushState: true }) simple-history.js Wednesday,

    March 28, 2012
  35. simple-history.js SimpleHistory.pushState(fragment[, state]) SimpleHistory.replaceState(fragment [, state]) SimpleHistory.start(callback) Wednesday, March 28,

    2012
  36. pushState example Wednesday, March 28, 2012

  37. $("a:not([href^=http])").click(function(event) { if (event.metaKey || event.shiftKey || event.ctrlKey) { return;

    } event.preventDefault(); SimpleHistory.pushState(this.href); }); Wednesday, March 28, 2012
  38. SimpleHistory.start(function(url, state) { var parts = URL.parse(url); var path =

    parts.path; var index = path === "/"; $(document.body).toggleClass("indexActive", index); if (!index) { var photo = photos.lookup(path); $("#photo img").attr("src", photo.src); $("#photo p").text( photo.description ); } }); Wednesday, March 28, 2012
  39. What about hashchanges? Wednesday, March 28, 2012

  40. twitter.com/bassistance vs twitter.com/#!/bassistance What about hashchanges? Wednesday, March 28, 2012

  41. twitter.com/bassistance vs twitter.com/#!/bassistance Never available on server What about hashchanges?

    Wednesday, March 28, 2012
  42. twitter.com/bassistance vs twitter.com/#!/bassistance Never available on server Only as fallback

    What about hashchanges? Wednesday, March 28, 2012
  43. twitter.com/bassistance vs twitter.com/#!/bassistance Never available on server Only as fallback

    Not necessary, as we’ll see soon What about hashchanges? Wednesday, March 28, 2012
  44. Wednesday, March 28, 2012

  45. pushState, not hashchange! Wednesday, March 28, 2012

  46. HTML5 history API, part 2 Wednesday, March 28, 2012

  47. HTML5 history API, part 2 history.pushState(..., state) Wednesday, March 28,

    2012
  48. HTML5 history API, part 2 history.pushState(..., state) history.replaceState(..., state) Wednesday,

    March 28, 2012
  49. Classic page Wednesday, March 28, 2012

  50. Classic page Wednesday, March 28, 2012

  51. Classic page Wednesday, March 28, 2012

  52. Single page Wednesday, March 28, 2012

  53. Single page Wednesday, March 28, 2012

  54. Single page Wednesday, March 28, 2012

  55. replaceState example Wednesday, March 28, 2012

  56. $(document).scroll(function() { var parts = URL.parse(location.href); if (parts.path !== "/")

    { return; } var scrollTop = $(document).scrollTop(); SimpleHistory.replaceState(location.pathname, { scroll: scrollTop }); }.debounce()); Wednesday, March 28, 2012
  57. SimpleHistory.start(function(url, state) { [...] if (state.scroll) { var scrollTop =

    state.scroll; setTimeout(function() { window.scrollTo(0, scrollTop); }, 50); } [...] }); Wednesday, March 28, 2012
  58. replaceState, not break state Wednesday, March 28, 2012

  59. First page load Wednesday, March 28, 2012

  60. Load everything static up front First page load Wednesday, March

    28, 2012
  61. Load everything static up front Initialize First page load Wednesday,

    March 28, 2012
  62. Load everything static up front Initialize Load data First page

    load Wednesday, March 28, 2012
  63. Load everything static up front Initialize Load data First page

    load Wednesday, March 28, 2012
  64. Load everything static up front Initialize Load data First page

    load Wednesday, March 28, 2012
  65. First impression Wednesday, March 28, 2012

  66. Server side rendering Wednesday, March 28, 2012

  67. Server side rendering access API directly Wednesday, March 28, 2012

  68. Server side rendering access API directly render templates Wednesday, March

    28, 2012
  69. Server side rendering access API directly render templates share code

    with client Wednesday, March 28, 2012
  70. Wednesday, March 28, 2012

  71. server rendering example Wednesday, March 28, 2012

  72. var connect = require('connect'); var fs = require('fs'); var handlebars

    = require('handlebars'); var url = require('url'); var photos = require('./app/gallery/photos'); Wednesday, March 28, 2012
  73. var template = handlebars.compile( fs.readFileSync('app/gallery/index.html', 'utf8') ); Wednesday, March 28,

    2012
  74. connect.createServer( connect.router(route), connect.static("app") ).listen(8085, "localhost", function() { console.log('Server running: http://localhost:8085');

    }); Wednesday, March 28, 2012
  75. function route(app) { app.get(/.../, function(request, response, next) { [...] });

    app.post(/.../, function(request, response, next) { [...] }); } Wednesday, March 28, 2012
  76. app.get(/^\/photos\/.+/, function(request, response) { var photo = photos.lookup(request.url); response.end(template({ src:

    photo.src, description: photo.description, photos: photos.data })); }); Wednesday, March 28, 2012
  77. app.get(/^\/photos\/.+/, function(request, response) { var photo = photos.lookup(request.url); response.end(template({ src:

    photo.src, description: photo.description, photos: photos.data })); }); Wednesday, March 28, 2012
  78. app.get(/^\/photos\/.+/, function(request, response) { var photo = photos.lookup(request.url); response.end(template({ src:

    photo.src, description: photo.description, photos: photos.data })); }); Wednesday, March 28, 2012
  79. var photo = photos.lookup(path); $("#photo img").attr("src", photo.src); $("#photo p").text( photo.description

    ); Wednesday, March 28, 2012
  80. // double client/server export var photos; if (typeof exports !==

    'undefined') { photos = exports; } else { photos = {}; } photos.data = [{ src: "/alligators/DSC_1165.jpg", description: "Not a crocodile" }, ...]; photos.lookup = function(url) { return photos.data.filter(function(photo) { return photo.url == url; })[0]; }; Wednesday, March 28, 2012
  81. serverside rendering, not slow first page load Wednesday, March 28,

    2012
  82. What about the JSON API? Wednesday, March 28, 2012

  83. JSON API Wednesday, March 28, 2012

  84. JSON API designed for third-parties only Wednesday, March 28, 2012

  85. JSON API designed for third-parties only not for the mothership

    Wednesday, March 28, 2012
  86. JSON API designed for third-parties only not for the mothership

    n+1 hell Wednesday, March 28, 2012
  87. API wrapper Wednesday, March 28, 2012

  88. API wrapper batching Wednesday, March 28, 2012

  89. API wrapper batching joining Wednesday, March 28, 2012

  90. API wrapper batching joining personalization Wednesday, March 28, 2012

  91. API wrapper example Wednesday, March 28, 2012

  92. app.get(/^\/pair\/result$/, function(request, response) { var term = url.parse(request.url, true).query.term; pair(term,

    function(result) { response.end(JSON.stringify(result)); }); }); Wednesday, March 28, 2012
  93. module.exports = function(tag, callback) { var replies = 0; var

    content = {}; function done(type, result) { content[type] = result; replies += 1; if (replies === 2) { callback(content); } } request.get(flickrUrl(tag), function(err, res, body) { done('flickr', parseFlickr(body)); }); request.get(soundcloudUrl(tag), function(err, res, body) { done('soundcloud', parseSoundcloud(body)); }); }; Wednesday, March 28, 2012
  94. API wrapper, not n+1 hell Wednesday, March 28, 2012

  95. What could possibly go wrong? Wednesday, March 28, 2012

  96. In space no one can hear you scream Wednesday, March

    28, 2012
  97. In browsers no one can hear your code scream Wednesday,

    March 28, 2012
  98. var up = new Error("omfg"); throw up; Wednesday, March 28,

    2012
  99. Wednesday, March 28, 2012

  100. Error handling Wednesday, March 28, 2012

  101. Error handling Catch on client Wednesday, March 28, 2012

  102. Error handling Catch on client Send to server Wednesday, March

    28, 2012
  103. Error handling Catch on client Send to server Aggregate Wednesday,

    March 28, 2012
  104. Error types Wednesday, March 28, 2012

  105. Error types SyntaxError Wednesday, March 28, 2012

  106. Error types SyntaxError TypeError Wednesday, March 28, 2012

  107. Error types SyntaxError TypeError Ajax Wednesday, March 28, 2012

  108. $("#breakz").click(function() { $.get("/not/exists"); this.does.not.exist(); }); Wednesday, March 28, 2012

  109. window.onerror = function(message, file, line) { logError("global", message, file +

    ":" + line); }; Wednesday, March 28, 2012
  110. $(window).bind("error", function(event) { var original = event.originalEvent; logError("global", original.message, original.filename

    + ":" + original.lineno); }); Wednesday, March 28, 2012
  111. $(document).ajaxError(function(event, xhr, options, error) { logError("ajax", error + ":" +

    xhr.responseText, options.url); }); Wednesday, March 28, 2012
  112. function logError(type, message, detail) { $.post("/errorlogger", { type: type, message:

    message, detail: detail }); } Wednesday, March 28, 2012
  113. app.post("/errorlogger", function(request, response) { var form = new formidable.IncomingForm(); form.parse(request,

    function(err, fields, files) { console.log("[CLIENT " + fields.type + " error]", fields.message, fields.detail); response.end(""); }); }); Wednesday, March 28, 2012
  114. Error handling example Wednesday, March 28, 2012

  115. Aggregation options Wednesday, March 28, 2012

  116. Custom Aggregation options Wednesday, March 28, 2012

  117. Custom Analytics Aggregation options Wednesday, March 28, 2012

  118. Custom Analytics Aggregation options Wednesday, March 28, 2012

  119. function logError(type, message, detail) { _gaq.push(['_trackEvent', type, message, detail]); }

    Wednesday, March 28, 2012
  120. Custom Analytics Airbrake, BugSense Aggregation options Wednesday, March 28, 2012

  121. Wednesday, March 28, 2012

  122. CATCH THEM ALL Wednesday, March 28, 2012

  123. Worth the trouble? Wednesday, March 28, 2012

  124. Worth the trouble? Better user experience Wednesday, March 28, 2012

  125. Worth the trouble? Better user experience Good first impression Wednesday,

    March 28, 2012
  126. Worth the trouble? Better user experience Good first impression Good

    response times Wednesday, March 28, 2012
  127. Worth the trouble? Better user experience Good first impression Good

    response times Stable product Wednesday, March 28, 2012
  128. THE END Wednesday, March 28, 2012

  129. Further reading: https://github.com/jzaefferer/pitfalls-examples Short link: http://git.io/Hirx3Q Further meeting: @cgnjs -

    colognejs.de - 12.4.2012, MVC frameworks Feedback please: http://www.surveymonkey.com/s/2F63MML Short link: http://svy.mk/xtVS2F @bassistance - joern.zaefferer@gmail.com Wednesday, March 28, 2012