Tips and Tricks Learned from Tree V8
Tyler Graf
Tech Lead - Tree Web Gold
David Crowther
Senior Web Dev - Tree Web Gold


Speed Sensitive View
What It Is
- Simple pedigree outline while panning

Why It's Cool
- Uses RUM to implement feature (not just to measure)
- Represents "How else could I use this?"
- Reduces workload for the device
How It Works
- RUM is used to determine 'slow'
- 3 SVG layers are maintained
- Outline is kept in sync with main content
- Outline is shown when panning on 'slow' device
- Main content position is updated when panning ends
FS Cache
Client-side Cache
Goals
- Cache heavily (Bandwidth $$$$)
- Conditional GET
- Encrypt cache (living people/relationships)
- Offline?
localForage
- Promise-based API
- IndexedDB
- Web SQL
- Local/Session storage
- Memory
- Browser support
- Name-spaced cache (2 layers)
const cache = FS.cache.instance({
storeName: 'names',
dbName: 'everything'
});
cache.setItem('MMMM-MMM','Tyler')
.then(data => {
// do something
});
cache.getItem('MMMM-MMM')
.then(name => {
// do something
});
Encryption
- Web cryptography API
- Hash generated for each user—used for symmetric encryption
- Base64 encode everything
Conditional Get
Cold Cache
Node Proxy
tree-data
person.version = `i3j2li4m4ja`
200 w/ person obj
GET /proxy/tree-data/person/MMMM-MMM
GET /tree-data/person/MMMM-MMM
200 w/ person obj
Warm Cache
Node Proxy
tree-data
compare old hash with new hash
204 w/ no payload
GET /tree-data/person/MMMM-MMM
200 w/ person obj
(object hash)
header: x-version=i3j2li4m4ja
200 w/ new person obj
OR
GET /proxy/tree-data/person/MMMM-MMM
Make It Resilient
- Don't worry about .setItem returning
- Handle cache read error
return cache.getItem(pid)
.then(person=>{
if(person){
renderperson(person); //we use redux to dispatch here
return service.getperson(pid, person.version);
}
return service.getperson(pid, null);
})
.then(person=>{
// setItem returns a promise but it could fail (do we care?)
cache.setItem(pid, person);
renderperson(person);
})
.catch(err=>{
if(err==='latest cached'){
return;
}
});
Conditional Get Results
last 7 days

200: 56% ~1GB
AVG Payload: ~2KB
D3 Rendered Pedigree
SVG?
foreignObjectAbsolutely Position Everything
<svg>
<g>
<foreignObject>
<div>
<fs-person name="Tyler Graf"></fs-person>
</div>
</foreignObject>
</g>
</svg>
var svg = d3.select("svg"),
g = svg.append("g");
g.append('foreignObject')
.append('xhtml:div')
.append('div')
.html('<fs-person name="Tyler Graf"></fs-person>');
HTML
- D3 renders HTML
- Even web components
- 2 pedigrees overlaying eachother
- HTML for nodes
- SVG for connecting lines
<section>
<fs-person name="Tyler Graf"></fs-person>
</section>
var section = d3.select("section");
section.append('fs-person')
.attr('name', 'Tyler Graf');
rollup.js
- We only need 3 D3 modules
- Custom up-to-date build
// rollup.config.js
import node from "rollup-plugin-node-resolve";
export default {
entry: "d3-rollup.js",
format: "iife",
name: "d3",
plugins: [node()]
};
// d3-rollup.js
export {
zoom,
zoomIdentity,
zoomTransform,
} from "d3-zoom";
export {
stratify,
tree,
} from "d3-hierarchy";
export {
event,
select,
selectAll,
} from "d3-selection";
//gulpfile.js
const rollup = require('rollup-stream');
const source = require('vinyl-source-stream');
var d3 = rollup('rollup.config.js')
.pipe(source('d3.js'))
.pipe(gulp.dest('components/d3/'));
// package.json
...
"d3-hierarchy": "^1.0.3",
"d3-selection": "^1.0.3",
"d3-zoom": "^1.1.1",
...
75KB => 17KB (gzipped)
Polymer
Making Changes
-
P1 -> hybrid -> P2
-
<template is='dom-bind'> ==> <dom-bind><template>
<template is="dom-bind">
</template>
<dom-bind>
<template is="dom-bind">
<fs-demo id="demo" pid="{{pid}}"></fs-demo>
<h2>Active fs-watch Demo</h2>
<fs-watch pid="[[pid]]"></fs-watch>
</template>
</dom-bind>
Polymer 1.0
Polymer 1 (hybid)
Making Changes
P1 -> hybrid -> P2
<template is='dom-bind'> ==> <dom-bind><template>
<dom-if> is different between hybrid & P2
-
P1 -> hybrid -> P2
-
<template is='dom-bind'> ==> <dom-bind><template>
<dom-if>
<template is="dom-if" if="[[someFunction(myParam)]]">
</template>
</dom-if>
<dom-if if="[[someFunction(myParam)]]">
<template>
</template>
</dom-if>
Polymer 1 (hybrid???--remove the <dom-if>)
Polymer 2
Making Changes
-
P1 -> hybrid -> P2
-
<template is='dom-bind'> ==> <dom-bind><template>
-
<dom-if> is different between hybrid & P2
-
<custom-style> needs to surround <style> or mix-ins won't work
-
P1 -> hybrid -> P2
-
<template is='dom-bind'> ==> <dom-bind><template>
-
<dom-if> is different between hybrid & P2
<custom-style>
<style include="demo-pages-shared-styles">
demo-snippet.dark {
--demo-snippet-demo: {
background-color: #333331;
};
}
</style>
</custom-style>
Polymer 2
Watch Out
-
Look for indirect error with computed properties
-
DOM observer functions (don't forget the observed property)
<fs-icon-eol id="expandArrow" class="expand-arrow"
icon="[[_getIcon('expand, colorScheme)]]"></fs-icon-eol>
Watch Out (more)
-
With P2, always call super.callback in any of the callbacks: super.ready() & super.connectedCallback()
ready() {
super.ready();
}
Polymer 2
semver
^1.3.2
=== 1.4.0
~1.3.2
=== 1.3.x
Latest: 1.4.0
Latest: 1.4.0
^0.0.3
=== 0.0.3
^0.1.3
=< 0.1.x
^1.0.0-beta
=== 1.0.0
Latest: 0.0.6
Latest: 0.3.0
Latest: 1.0.0
Pinned
Minor
Major
fs-demo

What It Does For Me
-
Allows components to run in isolation
-
Provides authentication
-
Provides an example PID list
-
Provides a language selector
Run In Isolation

Authentication

Example PID List

Language Selector

Memory Leak
What's a memory leak?
- Remove DOM nodes
- event listener still attached
- a reference still exists to that dom node in javascript
Event Listener
class MyElement extends Polymer.Element {
connectedCallback(){
super.connectedCallback();
document.addEventListener('thing-happened', function(){
this.doStuff();
}.bind(this);
}
}
class MyElement extends Polymer.Element {
connectedCallback(){
super.connectedCallback();
this._thingHappenedEH = this._handleThingHappened.bind(this);
document.addEventListener('thing-happened', this._thingHappenedEH);
}
disconnectedCallback(){
super.disconnectedCallback();
document.removeEventListener('thing-happened', this._thingHappenedEH);
}
_handleThingHappened(evt){
this.doStuff();
}
}
class MyElement extends Polymer.Element {
connectedCallback(){
super.connectedCallback();
document.addEventListener('thing-happened', this._handleThingHappened.bind(this));
}
disconnectedCallback(){
super.disconnectedCallback();
document.removeEventListener('thing-happened', this._handleThingHappened.bind(this));
}
_handleThingHappened(evt){
this.doStuff();
}
}
JS Reference to element
var _instances = [];
window.OakAJAXBehavior = {
set ajaxBase(val) {
_ajaxBase = val;
_instances.forEach(function(instance) {
if (instance && instance._setAjaxBase) {
instance._setAjaxBase(_ajaxBase)
}
});
},
ready: function() {
// Save off a ref to the consuming object so we can update
// all of the components when needed
_instances.push(this);
}
};
var _instances = [];
window.OakAJAXBehavior = {
set ajaxBase(val) {
_ajaxBase = val;
_instances.forEach(function(instance) {
if (instance && instance._setAjaxBase) {
instance._setAjaxBase(_ajaxBase)
}
});
},
ready: function() {
// Save off a ref to the consuming object so we can update
// all of the components when needed
_instances.push(this);
},
/**
* Clean up _instances.
*/
detached: function() {
_instances = _instances.filter(function(_instance) {
return this !== _instance;
}.bind(this));
}
};
Expanding Pedigrees

demo
How to Identity the leak
Go And Do

Tips and Tricks Learned from Tree v8
By David Crowther
Tips and Tricks Learned from Tree v8
- 882