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
- Build ordered & keyed lists
- Loop over virtual children
- Find match in ordered/keyed list
- Invoke diff on match or null
- Insert into parent if different from index
no
yes
append to unkeyed
add to keyed map
Diff: children
has key?
for each virtual child
- Build ordered & keyed lists
- Loop over virtual children
- Find match in ordered/keyed list
- Invoke diff on match or null
- 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 textAvoid DOM gettersAvoid 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,815