into the void 0
;

Who is this guy

@_developit  on Twitter

@developit  on GitHub

serial library author

Constraints

are

challenges

ADHD

Background

  • Carbyn
  • "HTML5 OS"
  • custom DOM atop the real DOM​

(artistic liberty)

Background

Look familiar?

This code was
written in 2008.

JSX

XML-like expression

compiled to a function call

<div id="one">
  Hello
</div>


<Foo hello />
h('div', { id:'one' },
  'Hello'
);


h(Foo, { hello: true })

JSX Explained

  • Inside braces:  JS expression
  • Capitalized name:  reference
let world = 'World!';

<div one={ 1 }>
  Hello, { world }
</div>

let Link = 'a';
<Link>Test</Link>
var world = 'World!';

h('div', { one: 1 },
  'Hello, ', world
);

var Link = 'a';
h(Link, 'Test');

JSX Explained

  • AKA createElement()
  • Calls inserted in place of every <tag>

  h(
​    nodeName,    // String / Function
    attributes,  // Object / null
    ...children  // any remaining args
  )

Reviver Function

So... hyperscript?


  h('div', { id:'one' },
    'Hello'
  )

  h('div', { id:'one' },
    'Hello'
  )

 h(Foo, { hello: true })

  h('div#one', 'Hello')

Hyperscript

Transpiled JSX

Virtual DOM

Virtual DOM

Objects representing

a DOM tree structure

Virtual DOM

JSX

hyperscript

Virtual DOM

syntax

function calls

objects

transform

invoke

We write this:

 <div id="foo">
   Hello!
   <br />
 </div>

... which transpiles to this:

 h('div', { id:'foo' },
   'Hello!',
   h('br')
 )

... which returns this:

 {
   nodeName:'div',
   attributes: { id:'foo' },
   children: [
     'Hello!',
     { nodeName:'br' }
   ]
 }

  function h(nodeName, attributes, ...children) {

    return { nodeName, attributes, children };

  }

Simple Version

 function render(vnode) {  
   if (typeof vnode==='string')
     return document.createTextNode(vnode)

   let node = document.createElement(vnode.nodeName)

   for (let name in Object(vnode.attributes))
     node.setAttribute(name, vnode.attributes[name])

   for (let i=0; i<vnode.children.length; i++)
     node.appendChild(render(vnode.children[i]))

   return node
 }

Simple Renderer

 function diffAttributes(node, vnode) {
   for (let name in Object(vnode.attributes))
     node.setAttribute(name, vnode.attributes[name])
 }

Attributes or Properties?

 function diffProperties(node, vnode) {
   Object.assign(node, vnode.attributes)
 }
 function diffProps(node, vnode) {
   for (let name in Object(vnode.attributes)) {
     let value = vnode.attributes[name]
     if (name in node) {
       node[name] = value
     }
     else {
       node.setAttribute(name, value)
     }
   }
 }

?

?

Does it work?


 let vdom = (
   <div id="foo">
     <h1>Hello</h1>
     <ol>
       <li>One</li>
       <li>Two</li>
     </ol>
   </div>
 )

 let dom = render(vdom)

 document.body.appendChild(dom)

YUP

Deploy It

Diffing

without magic

WHAT IF...

Instead of rendering,

we diffed the VDOM objects

against the current DOM

and applied any differences?

Virtual

Actual

 {
   nodeName: 'div',
   attributes: {
     key: 'value'
   },
   children: [
     'text node'
   ]
 }
 Element(
   nodeName: 'div',
   attributes: {
     getItem('key'),
     setItem('key', 'value')
   },
   childNodes: [
     TextNode('text node')
   ]
 )

DOM

DOM

Comparable

Virtual DOM DOM Declarative DOM Imperative
nodeName nodeName createElement(nodeName)
attributes[] attributes{} setAttribute(key, value)
getAttribute(key): value
children[] childNodes[] appendChild(child)
removeChild(child)
insertBefore(child, next)
replaceChild(old, child)

compare

apply

Parallels

Diff Process

<type>
children
attributes / props

new / updated?

1

2

3

Diff: type

is node component?

diff()

create/update component

render component

same type?

no

yes

no

yes

create new & replace

update in-place

Diff: children

has key?

for each child node

no

yes

append to unkeyed

add to keyed map

find match in

keyed / unkeyed

for each virtual child

diff against virtual child

insert at current index

remove unused children

1

2

3

Children & keys

find child Element with same key

if VNode has a key:

find next unused child of same type

otherwise:

no match?  create a new element.

Component / Text / nodeName

What are keys for?

keys attribute meaningful order to a set of uniform elements.

 <li>one</li>
 <li>two</li>
 <li>three</li>
 <li>one</li>
 <li>three</li>
 <li>three</li>
 <li>one</li>
 <li>three</li>
<li key="1">one
<li key="2">two
<li key="3">three
 <li>one</li>
 <li>two</li>
 <li>three</li>
<li key="1">one
<li key="3">three

Diff: attributes/props

for each
old attribute

not in new attributes?

set to undefined

for each
new attribute

differs from old value?

set to
new value

1

2

Performance

Benchmark everying

"The DOM is slow"

YOUR DOM is slow

"The DOM is slow"

DOMination

use Text nodes

for text


  // Create it
  text = document.createTextNode('some text')

  // Insert it
  parent.insertBefore(text, <before | null>)

  // Flip it & reverse it
  text.nodeValue = 'ʇxǝʇ ǝɯos'

Text.nodeValue

almost 50% faster

DOM wanna miss a thing

avoid (DOM) getters


  // slow
  if (node.nodeType===3) { }

  // fast
  if (node.splitText) { }

  // ludicrous speed
  if (node.splitText!==undefined) { }

breaks when cross-document

getters

simple prototype property

DOM & dommer

avoid Live NodeLists


 // slow
 for (let i=parent.childNodes.length; i--; ) {
   parent.removeChild(parent.childNodes[i])
 }

 // fast
 while ( child = parent.lastChild ) {
   parent.removeChild(child)
 }

(bench setup overhead)

childNodes

lastChild

Measure

Always be profiling

Performance

  • Chrome Dev Tools Timeline, so good

Something to obsess over.

Performance

  • IRHydra2
  • @mraleph
  • see how/why
  • shows deopts

git.io/hydraosx

Be Explicit

Don't accidentally ToBoolean


 // don't write this:
 if (obj.props)

 // if you mean this:
 if (obj.props!==undefined)

could be 0, '', null...

Inline Helpers

Functions can be too generic.


 function hook(obj, name, a, b) {
   return obj[name] && obj[name](a, b);
 }

 hook(obj, 'foo', 1, 2);
 hook(obj, 'foo', 'a', {b:'c'});


 obj.foo(1, 2);

deopts after a few different values

has its own inline cache

could be anything

Short-Circuitting

The cheapest function call
is the one you never make

- unknown

Fin

making changes

based on actual data

YOU

Monomorphism

monomorphic

fn({ a: 1 })
fn({ a: 2 })

megamorphic

fn({ a: 1, b: 2 })
fn({ b: 2, a: 1 })
fn('a', 1)
fn(div)
fn(span)

polymorphic

fn({ a: 1 })
fn({ b: 2 })
fn({ a: 1 })
fn({ a: 1 }, 1)

best

worst

better


  fn = o => o.a

Benchmark!

Monomorphic

Guarded property access

*here be dragons

Batch Writes

  • Don't render faster than you can paint
  • Don't render and immediately re-render
  • Best technique varies

requestAnimationFrame

setTimeout

MutationObserver

Promise.then()

SCRAP



     // like 0ms
     let child = parent.lastChild
     while (child) {
       let prev = child.previousSibling
       child.remove()
       child = prev
     }

Diff: children

has key?

for each child node

  1. Build ordered & keyed lists
  2. Loop over virtual children
  3. Find match in ordered/keyed list
  4. Invoke diff on match or null
  5. Insert into parent if different from index

no

yes

append to unkeyed

add to keyed map

Diff: children

has key?

for each virtual child

  1. Build ordered & keyed lists
  2. Loop over virtual children
  3. Find match in ordered/keyed list
  4. Invoke diff on match or null
  5. Insert into parent if different from index

no

yes

find match in unkeyed

find in keyed map

diff against virtual child

Today

Today

  • a growing community
  • expanding focus to Developer Experience
  • lots of discussion & contribution
  • in use at many companies

Use-Cases

  • Self-contained web widgets / embeds
  • Progressive Web Apps
  • Pieces of an existing application
  • Component-by-component migration
  • Web Components

Non-Use-Cases

  • React Native
  • Alternative Renderers
  • Certain 3rd-party libs
  • Uncomfortable with the DOM

New Use-Cases

Preact

In 30 seconds

Origins

  • remembered: hyperscript

Accidentally wrote another template engine

Sad me

Hyperscript

<div id="one">
  Hello
</div>


<Foo hello />
h('div', { id:'one' },
  'Hello'
);


h(Foo, { hello: true })

Remember hyperscript?

It's behind everything now

JSX

From blog post "WTF is JSX"

// a "partial" that does a filtered loop
function foo(items) {
  return items.map( p => <li>{p}</li> );
}

let vdom = (
  <div id="foo">
    <p>Look, a simple JSX DOM renderer!</p>
    <ul>{ foo(ITEMS) }</ul>
  </div>
)

document.body.appendChild( render(vdom) );

I created Preact
to teach myself
how React worked

Preact

  • started out as a CodePen
  • animating hundreds of elements
  • obsessively profiled & tracked FPS
  • rapidly tested out ideas
  • recycling for memory usage?
  • cache props on DOM elements?
  • cache normalized nodeName?
  • cost of attributes vs properties

Paul Lewis (@aerotwist)

Pot Stirrer

"React + Performance = ?"

I wonder...

Results

Decent

Surprisingly Decent

Debounced rendering

Preact

Turns out,

Preact works a lot like Paul's Vanilla JS baseline.

Preact

  • a tiny, simplified React!
  • added support for "transient" components
  • started adding React-isms:
  • stateful components (classes)
  • lifecycle methods
  • compositional components
  • refs
  • context

this was super

annoying to do

Paul Lewis

...again!

"The Cost of Frameworks"

It seems to me that developer ergonomics should be less important than our users’ needs.

Preact

  • Small modules / publish all the things
  • Needed a catchy name
  • All the good ones taken

What if...

Instead of fighting the web,

we embraced it?

Idea

DOMination

  • Use Text nodes for text
  • Avoid DOM getters
  • Avoid Live NodeLists
  • Use CSS for styling
  • CSS contain:strict is blood magic
  • Use Microtasks for tight async
  • Write inlineable code

Diff: type

is vnode same type

diff

Component?

(function)

create/update component

render

same type

as DOM node?

same type

as DOM node?

no

yes

Lessons from IRHydra

  • monomophism
  • be explicit
  • inline helpers
  • short-circuit / fast path
  • when in doubt: benchmark!

Diff: children

find match in

keyed / unkeyed

for each virtual child

diff against virtual child

insert at current index

Children & keys


  function match(node, vnode) {
    if (vnode.key!=null) {
      return node.props.key === vnode.key
    }

    if (typeof vnode.nodeName==='function') {
      return node._constructor === vnode.nodeName
    }

    return node.nodeName === vnode.nodeName
  }

deck

By developit

deck

  • 2,610