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

Copy of Tips and Tricks Learned from Tree v8

By Tyler Graf

Copy of Tips and Tricks Learned from Tree v8

  • 836