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?

  • foreignObject
  • Absolutely 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