Background
Coming up
Models: making them work for you
CSS: making it work at all
Client-side performance, or "cheating"
The long running app
- Challenges
- Opportunities
- Faster loading
- Better experience
Leaks
var cache = {};
function sendRequest(url) {
if (!cache[url]) {
cache[url] = $.get(url).fail(logError);
}
return cache[url];
}
Sharing is caring
var map = {};
function User(id) {
if (map[id]) {
return map[id];
}
map[id] = this;
this.initialize();
}
var userA = new User(123),
userB = new User(123);
userA === userB; // true
userA.set('name', 'Sparticus');
userB.get('name'); // 'Sparticus'
var userC = new User(123);
userC.get('name'); // 'Sparticus'
Wait a second
Isn't this a massive memory leak?
It totally is, so...
var map = {},
counts = {}; // <-- usage counter
function User(id) {
if (map[id]) {
++counts[id]; // <-- increase on access
return map[id];
}
map[id] = this;
counts[id] = 1; // <-- set to 1 on first creation
this.initialize();
}
User.prototype.release = function () {
--counts[this.id];
}
This is a slight simplification...
In use
MyView = View.extend({
initialize: function (options) {
this.model = new Sound(options.soundId);
},
dispose: function () {
this.model.release();
}
});
Still need to be careful to release all that you create
Nested API Responses
api.soundcloud.com/tracks/97602814.json
{
// ...snip...
title: "02 daftside - within",
uri: "https://api.soundcloud.com/tracks/97602814",
user: {
id: 48554944,
kind: "user",
permalink: "daftside-2",
avatar_url: "https://i1.sndcdn.com/avatars...",
username: "DARKSIDE (USA)"
},
user_id: 48554944
}
Managing models
Techniques like the identity map can help you make full use of the data you have...
...but be careful to manage that data over your app's lifetime.
CSS Management
body#pages.sounds #main-wrapper #main-wrapper-inner #soundpage #primary .social-sharing .share-plugin.last .twitter-share-button {
width: 101px !important;
}
5 ids
5 classes
8 descendant selectors
!important
wat
The problems
Fragility
Speed
File size
Maintainability
Our solution
Application built from lots of small views
One CSS file per view
In the template, each element gets a namespaced class
Inspired by BEM from Yandex
.soundTitle__title {
max-width: 100%;
display: inline-block;
}
.soundTitle__tagContainer {
line-height: 12px;
}
.soundTitle__playButton {
margin-right: 5px;
margin-top: -15px;
}
But...
"It makes my eyes bleed"Chris Eppstein, SASS/Compass
CSS ↝ JS
define("views/sound/title.css", [],
function(require, exports, module) {
var style = module.exports = document.createElement("style");
style.appendChild(
document.createTextNode(
'.soundTitle__info{display...}'
)
);
}
);
View.extend({
css: require('views/sound/title.css'),
render: function () {
if (!this.css.parentNode) {
document.body.appendChild(this.css);
}
// ...
}
});
CSS as modules
Fragility
Speed
File size
Maintainability
My waveforms
Let me show you them
The situation
New Waveforms
New style: sharp, no anti-aliasing.
New playlist style
Flexible
Solution:
Canvas!
Problem:
Data!
Solution:
Images as data source
Problem:
Performance!
Dubstep fans
Rendering time: 1352ms
Gradual improvements
- Most efficient detection algorithm
- Caching scaled images
- Typed Arrays
WebWorkers
Breakthrough realisation:
Who gives a crap anyway?
Still kept improving
...by giving up
- Move calculation to the back end
- Ported algorithm to Go
- Front end receives JSON, everyone happy
End result
Before: 1352ms
After: 10ms
Take away
Think about how to cheat.
Take off the engineer hat.
Think about what's important, not what's "correct".