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

Copy of Tips and Tricks Learned from Tree v8
By Tyler Graf
Copy of Tips and Tricks Learned from Tree v8
- 836