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

Ember.js : Lessons from the trenches

Ember.js : Lessons from the trenches

From SEO to a highly performant infinite scrolling implementation, I will show you a handful collection of tips & tricks gathered by working on the largest Ember.js open-source application: Discourse.

B7797beb47cfb7aa0fe60d09604aaa09?s=128

Régis Hanol

January 30, 2014
Tweet

More Decks by Régis Hanol

Other Decks in Programming

Transcript

  1. None
  2. Régis Hanol @ZogStriP

  3. None
  4. CODING HORROR

  5. None
  6. None
  7. Ember.js Lessons from the trenches

  8. None
  9. Browser Application Framework

  10. Long-lived

  11. Handlebars

  12. <div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div>

  13. Convention Configuration

  14. this.route('user');

  15. this.route('user'); UserRoute /user

  16. this.route('user'); UserRoute User LOADS /user

  17. this.route('user'); UserRoute User LOADS UserController SETS MODEL
 ON /user

  18. this.route('user'); UserRoute User LOADS UserController UserView SETS MODEL
 ON RENDERS

    /user
  19. this.route('user'); UserRoute User LOADS UserController UserView user.handlebars SETS MODEL
 ON

    RENDERS BASED ON /user
  20. this.route('user'); UserRoute User LOADS UserController UserView user.handlebars SETS MODEL
 ON

    RENDERS GETS STATE FROM BASED ON /user
  21. Web Components

  22. None
  23. {{!-- components/awesome-button —-}} <button class="btn"> <i {{bind-attr class="fa icon"}} />

    {{text}} </button>
  24. {{!-- post.handlebars --}} {{awesome-button text="Reply" icon="fa-plus"}} {{!-- components/awesome-button —-}} <button

    class="btn"> <i {{bind-attr class="fa icon"}} /> {{text}} </button>
  25. SEO

  26. None
  27. <noscript> <%= render "header" %> <%= yield %> </noscript>

  28. None
  29. None
  30. PRELOAD STORE

  31. Browser Server

  32. Browser Server GET /t/awesome-topic/42

  33. Browser Server GET /t/awesome-topic/42 <html>...</html>

  34. Browser Server GET /t/awesome-topic/42 <html>...</html> HTML PARSING

  35. Browser Server GET /t/awesome-topic/42 <html>...</html> GET /styles.css GET /app.js HTML

    PARSING
  36. Browser Server GET /t/awesome-topic/42 <html>...</html> GET /styles.css GET /app.js HTML

    PARSING BOOT STRAP
  37. Browser Server GET /t/awesome-topic/42 <html>...</html> GET /styles.css GET /app.js GET

    /topics/42.json { topic: ... } HTML PARSING BOOT STRAP
  38. None
  39. Browser Server GET /t/awesome-topic/42 <html>...</html> GET /styles.css GET /app.js GET

    /topics/42.json { topic: ... }
  40. # application_controller.rb def store_preloaded(key, json) @preloaded ||= {} @preloaded[key] =

    json end
  41. # topics_controller.rb ! @topic = ... ! respond_to do |format|

    format.html do store_preloaded("topic", @topic.to_json) render "topic" end ... end
  42. <!-- application.html.erb --> <%- if @preloaded.present? %> <script> <%- @preloaded.each

    do |key, json| %> PreloadStore.store("<%= key %>", <%= json %>); <% end %> </script> <%- end %>
  43. model: function() { // try the preload store first return

    PreloadStore.get("topic", function() { // hit the API return $.ajax("/topics/" + id + ".json"); }).then(function(result) { // instanciate a Topic return Discourse.Topic.create(result); }); }
  44. COMPUTED PROPERTY MACROS

  45. App.User = Ember.Object.extend({});

  46. var user = App.User.create({ name: “Régis Hanol” }); App.User =

    Ember.Object.extend({});
  47. console.log(user.get(“name”)); // Régis Hanol App.User = Ember.Object.extend({}); var user =

    App.User.create({ name: “Régis Hanol” });
  48. COMPUTED PROPERTIES

  49. App.User = Ember.Object.extend({ ! staff: function() { return this.get("moderator") ||

    this.get("admin"); }.property("moderator", "admin") ! });
  50. App.User = Ember.Object.extend({ ! staff: function() { return this.get("moderator") ||

    this.get("admin"); }.property("moderator", "admin") ! }); var mod = App.User.create({ moderator: true });
  51. console.log(mod.get("staff")); // true App.User = Ember.Object.extend({ ! staff: function() {

    return this.get("moderator") || this.get("admin"); }.property("moderator", "admin") ! }); var mod = App.User.create({ moderator: true });
  52. CACHED

  53. App.User = Ember.Object.extend({ ! validUsername: function() { return this.get(‘username’).match(/^[a-z0-9]+$/); }.property(‘username’);

    ! adult: function() { return this.get(‘age’) >= 18; }.property(‘age’); ! french: function() { return this.get(‘country’) === ‘FR'; }.property(‘country’); ! });
  54. App.User = Ember.Object.extend({ ! validUsername: Ember.computed.match('username', /^[a-z0-9]+$/), ! adult: Ember.computed.gte('age',

    18), ! french: Ember.computed.equal('country', ‘FR'), ! });
  55. alias and any bool collect defaultTo empty equal filter filterBy

    gt gte intersect lt lte map mapBy match max min none not notEmpty oneWay or setDiff sort union uniq
  56. COMPOSABLE

  57. App.User = Ember.Object.extend({ ! adult: Ember.computed.gte('age', 18), ! french: Ember.computed.equal('country',

    ‘FR’), ! canVote: Ember.computed.and(‘adult’, ‘french’) ! });
  58. RUN LOOP

  59. None
  60. this.set('productId', 1234); ! this.get('productId'); // new value ! $('#product-id').html() //

    still old value!
  61. WTFWHY?

  62. UPDATES are COALESCED

  63. this.set('productId', 1); this.set('productId', 2); this.set('productId', 3); this.set('productId', 4); ! //

    DOM is only updated once!
  64. HOW?

  65. EVENT QUEUE

  66. setTimeout(function () { // do something later! }, 1);

  67. RUN LOOP

  68. RUN LOOP

  69. sync

  70. sync actions

  71. sync actions render

  72. sync actions render afterRender

  73. sync actions render afterRender destroy

  74. sync actions render afterRender destroy

  75. sync actions render afterRender destroy 5 3 4 2 1

  76. sync actions render afterRender destroy 1 5 3 4 2

  77. sync actions render afterRender destroy 2 5 3 4 1

  78. sync actions render afterRender destroy 2 5 3 4 1

  79. sync actions render afterRender destroy 2 5 3 4 1

  80. sync actions render afterRender destroy 1 5 3 4 2

  81. sync actions render afterRender destroy 2 5 3 4 1

  82. sync actions render afterRender destroy 2 5 3 4 1

  83. sync actions render afterRender destroy 3 2 5 4 1

  84. sync actions render afterRender destroy 4 2 3 5 1

  85. sync actions render afterRender destroy 5 2 3 4 1

  86. this.set('productId', 1234); ! this.get('productId'); // new value ! Ember.run.schedule(‘afterRender’, function()

    { $('#product-id').html() // new value \0/ });
  87. YAGNI*

  88. YAGNI* *usually

  89. App.PostView = Ember.View.extend({ ! // right after it was inserted

    in the DOM didInsertElement: function() { var h = this.$().height(); // reflow this.$().height(h * 2); // reflow } ! });
  90. PostView x 50 Read/Write/Read/Write…

  91. 100 REFLOWS

  92. DE-INTERLACE

  93. App.PostView = Ember.View.extend({ ! didInsertElement: function() { var element =

    this.$(); h = element.height(); // reads ! Ember.run.schedule('afterRender', function() { element.height(h * 2); // writes }); } ! });
  94. 2 REFLOWS 50 Reads/50 Writes

  95. VIEW CLOAKING

  96. VIEW HIERARCHY

  97. None
  98. None
  99. PERFORMANCE

  100. PERFORMANCE More Views = Slower Rendering

  101. INFINITE SCROLLING

  102. UNLOAD OFF-SCREEN VIEWS

  103. VIEWPORT JUMPING

  104. CLOAKING

  105. None
  106. - Create a `CloakedView` instance ! - Listen for Window

    Scroll events ! = When off-screen # Record and fix height of parent view # Detach child instance ! = When on-screen # Re-attach child instance
  107. github.com/eviltrout/ember-cloaking EMBER-CLOAKING

  108. <!-- cloaked version --> {{cloaked-collection content=posts cloakView="post"}} <!-- regular version

    --> {{collection content=posts itemViewClass=“Discourse.PostView"}}
  109. 30% less RAM Running in Production

  110. SEO easiest first

  111. PRELOAD all the things!!!

  112. CP MACROS (ab)use them

  113. RUN LOOP schedule/coalesce

  114. CLOAKED VIEWS faster, less RAM

  115. THANKS! emberjs.com discourse.org