Mike Bartlett and Andrew Newdigate, founders of Gitter, discuss lessons learned building and scaling a realtime web application with the Marionette NY Community.
Unblocking The Main Thread Solving ANRs and Frozen Frames
Gitter marionette deck
1.
2. Who are these dudes?
suprememoocow
mydigitalself
Andrew Newdigate
Mike Bartlett
3. Gitter is where
developers come to talk
120 000 registered users
24 000 public communities
Active every minute of every day
502 releases in 1.5 years
4. Zawinksi’s Law
Every program attempts to expand until it can read mail.
Those programs which cannot so expand are replaced by ones which can.
10. Finding performance problems
Don’t optimise too early
Focus on CollectionViews, CompositeViews
Improving the performance 1000% on a view that gets rendered once in
the application isn’t going to make the slightest bit of difference.
12. Example of
using Timelines
$.tooltip is really slow
Solution: change the tooltip
behaviour to only initialise the
tooltip on the first mouseover event
fired on the element
1ms per tooltip to 0.005ms per
tooltip
13. Easy performance win: attachElContent
Override this method in your collection/composite child views
// Abbreviated version of the attachElContent MixIn we use on Gitter
attachElContent: function(html) {
if (typeof html === 'string') {
this.el.innerHTML = html;
return this;
}
this.$el.html(html);
return this;
}
16. Pre-rendering is good practice
Page indexing / SEO advantages to doing it
Perceived speed of page load is much faster
Avoid multiple reflows as the application loads
Less jankiness
17. Pre-rendering is messy
At the moment, done through a series of hacks:
• Server-side handlebars helpers
• Client-side Marionette extensions
Would be awesome to move this out into a semi-sane, open-source
library (or build it into Marionette!)
19. Isomorphic LayoutViews
In LayoutView’s before:show event
• If the region is empty, initialise ChildView as per normal:
• If the region already contains content, mount the ChildView on the
existing element:
view.showChildView(regionName,
new ChildView({ el: existingElement, template: false, ... options ... }));
view.showChildView(regionName, new ChildView({ ... options ... }));
20. Isomorphic CollectionViews
childViewOptions: function (model) {
if (!model.id) return;
var el = this.$el.find('> [data-id="' + model.id + '"]')[0];
if (!el) return;
return {
el: el,
template: false
};
},
<ul>
<li data-id="1">One</li>
<li data-id="2">Two</li>
<li data-id="3">Three</li>
</ul>
collection.reset([
{ id: “1”, name: “One” },
{ id: “2”, name: “Two” },
{ id: “3”, name: “Three” }
]);
24. People Roster Data
~300 characters
In a 5000 user room, that’s 1.4MB of JSON
Retina and non-retina avatar URLs
Unused fields, duplicate data, etc
{
"id": "5298e2d5ed5ab0b3bf04c980",
"username": "suprememoocow",
"displayName": "Andrew Newdigate",
"url": "/suprememoocow",
"avatarUrlSmall": "https://avatars1.githubuser
"avatarUrlMedium": "https://avatars1.githubuse
"gv": "3",
"v": 30
}
25. How we represent them now
77 characters
In a 5000 user room, that’s still 375KB.
Limit the list to the first 20 people
{
"id": "5298e2d5ed5ab0b3bf04c980",
"username": "suprememoocow",
"gv": "3",
"v": 30
}
27. .on is a code smell
Using jquery events
Backbone events
Also, beware of long running setTimeouts
this.ui.actionButton.on('click', function() {
window.alert('Yo');
});
this.model.on('change', function() {
window.alert('Yo');
});
28. Obvious solution
Use modelEvents, collectionEvents and events
modelEvents: {
'change': 'onChange'
},
events: {
'click @ui.badge': 'onBadgeClicked'
},
collectionEvents: {
'add reset sync reset': 'showHideHeader'
},
Use listenTo for listening to Backbone.Events
this.listenTo(model, 'change', function() { })
29. When you still need .on
Remember to cleanup after yourself
onClick: function() {
this.$someElement.on('mouseenter', ...);
this.longRunningTimer = setTimeout(function() {}, 60000);
},
onDestroy: function() {
this.$someElement.off();
clearTimeout(this.longRunningTimer);
},
33. MV* 101
This is how we’re taught to
structure MV* applications
at school.
34. Sometimes it’s easier to ignore the advice
We need to tell another view to do
something.
We’re in a rush, so we’ll just wire the
dependency in and fix it later.
var MyView = Mn.ItemView.extend({
...
onActionClicked: function() {
this.options.anotherView.doSomething();
},
})
var myView = new MyView({ anotherView:
anotherView });
36. This makes change hard
Just try to:
• Move a view within the view hierarchy
• Remove a view in a certain environment
(unauthenticated view, mobile, etc)
Let’s change things
around a bit…
40. Quick and dirty
A component needs to respond to an action and change another
component’s DOM…
Easiest solution: just use jquery
onClick: function() {
$('#action-button').hide();
}
41. c/c++ pointer arithmetic
In c/c++, it’s possible to use pointer arithmetic to directly modify the
contents of a location in memory.
I’m sure you will all agree: this is a VERY BAD IDEA!
bptr = (byte*) &data;
bptr = bptr + 5;
iptr = (int*) bptr;
(*iptr) = 0xcafebabe;
42. Now imagine…
Your DOM is a global memory shared by all the Javascript code running
in your app
Each view in your app manages a distinct piece of the global memory
Mutating another view’s DOM is a bit like using pointer arithmetic to
change it’s memory behind it’s back
Don’t do it!
45. Then
Client: AMD modules with RequireJS
Tests: run in a phantomjs
Server: commonjs modules with nodejs
Tests run in nodejs with mocha
46. Now
Client: commonjs modules with webpack
Server: commonjs modules with nodejs
Shared code is kept in shared
Shared code can be tested quickly using the nodejs and mocha, without
having to start a phantomjs browser
47. require.ensure();
// In your backbone router....
markdown: function() {
require.ensure(['views/markdown/markdownView'], function(require) {
var MarkdownView = require('views/markdown/markdownView');
appView.dialogRegion.show(new MarkdownView({}));
});
},
49. Code Debt
A lot of these problems as the result of technical debt. When we started
building the project we chose Backbone, and only later did we switch to
Marionette.
Initially, we treated Marionette as a neat extension of Backbone, for
things like CollectionViews etc so the transition was gradual and left a
lot of technical debt around.
Marionette 2 PR
50. From a small prototype to a large application
A lot of the pain we’ve experienced has been down to the fact that we
started off with a small application which has grown larger and larger.
Start as you mean to go on