What the v...DOM?

What the v...DOM?

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

April 18, 2018
Tweet

Transcript

  1. WHAT THE v...DOM? @stefanjudis

  2. STEFAN JUDIS @stefanjudis www.stefanjudis.com stefan.judis@contentful.com

  3. 2013

  4. "Super fast and revolutionary because of the virtual DOM
 and

    JSX." MAGIC!
  5. THERE IS NO MAGIC IN CODE!

  6. SOMETIMES
 IT FEELS 
 LIKE MAGIC

  7. TAKE A BREAK!

  8. Matthias

  9. IMPERATIVE

  10. Go out of the door and turn left.
 Get on

    Frankfurter Allee until you get to the Proskauer Straße.
 Go left at the park. Go to the end of the park.
 My house is #22.
  11. The imperative programming paradigm uses statements 
 that change a

    program's state. “
  12. DECLARATIVE

  13. My address is Berlin, Friedrichshain.

  14. “ The declarative programming paradigm expresses the logic of a

    computation without describing its control flow.
  15. Imperative function double (arr) { let results = []; for

    (let i = 0; i < arr.length; i++){ results.push(arr[i] * 2); } return results; } Declarative function double (arr) { return arr.map((item) => item * 2); }
  16. INTERFACES

  17. const $btn = document.getElementById('btn'); $btn.addEventListener('click', _ => { $btn.classList.toggle('is-highlighted'); $btn.innerText

    = $btn.innerText === 'Highlight me!' ? 'Please stop!' : 'Highlight me!'; }); Imperative UI
  18. Declarative UI <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please

    stop!': 'Highlight me!' } </button>
  19. <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight

    me!' } </button>
  20. export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op
  21. export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op THAT'S NOT JAVASCRIPT!?
  22. CODE TRANSFORMS

  23. ! & ? PARSE ! ~ ? TRANSFORM GENERATE

  24. ./example/input.js const year = (new Date()).getFullYear(); const msg = `:tada:

    Frontend Con ${year}! :tada:`; console.log(msg);
  25. None
  26. babeljs.io

  27. PARSE with Babylon

  28. const {parse} = require('babylon'); const code = ` const year

    = (new Date()).getFullYear(); const msg = ':tada: Frontend Con ${year}! :tada:'; console.log(msg); `; const ast = parse(code); ./my-own-code-transform/index.js
  29. AST ( Abstract Syntax Tree )

  30. “In computer science, an abstract syntax tree (AST), [...], is

    a tree representation of the abstract syntactic structure of source code written in a programming language.
  31. None
  32. { "type": "Program", "start": 0, "end": 105, "loc": { "start":

    { "line": 1, "column": 0 }, "end": { "line": 3, "column": 17 } } }
  33. { "type": "VariableDeclaration", "start": 0, "end": 40, "loc": { "start":

    { "line": 1, "column": 0 }, "end": { "line": 1, "column": 40 } }, "declarations": [...], "kind": "const" }
  34. { "type": "ExpressionStatement", "start": 88, "end": 105, "loc": { "start":

    { "line": 3, "column": 0 }, "end": { "line": 3, "column": 17 } }, "expression": {...} }
  35. const msg = `:tada: Frontend Con ${year}! :tada:`; ./example/input.js

  36. const msg = `:tada: Frontend Con ${year}! :tada:`; ./example/input.js VariableDeclaration

  37. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration
  38. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier
  39. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral
  40. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement
  41. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement Identifier
  42. ./example/input.js VariableDeclarator const msg = `:tada: Frontend Con ${year}! :tada:`;

    VariableDeclaration Identifier TemplateLiteral TemplateElement TemplateElement Identifier
  43. astexplorer.net

  44. ASTs are everywhere

  45. astexplorer.net

  46. ctfl.io/asts-in-graphql

  47. TRANSFORM with @babel/traverse

  48. VISITOR PATTERN ( design pattern described by the "Gang of

    Four" )
  49. const {parse} = require('babylon'); const code = `...`; const ast

    = parse(code); ./my-own-code-transform/index.js
  50. const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter ({node}) { if (node.type === 'TemplateElement') { node.value.raw = node.value.raw.replace(/:tada:/g, '\u{1F389}'); } } }); ./my-own-code-transform/index.js
  51. const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter ({node}) { if (node.type === 'TemplateElement') { node.value.raw = node.value.raw.replace(/:tada:/g, '\u{1F389}'); } } }); ./my-own-code-transform/index.js
  52. GENERATE with @babel/generator

  53. const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse'); const

    code = `...`; const ast = parse(code); traverse(ast, { enter (path) { /* ... */ } }); ./my-own-code-transform/index.js
  54. ./my-own-code-transform/index.js const {parse} = require('babylon'); const {default: traverse} = require('@babel/traverse');

    const {default: generate} = require('@babel/generator'); const code = `...`; const ast = parse(code); traverse(ast, { enter (path) { /* ... */ } }); console.log(generate(ast).code);
  55. None
  56. BABEL PLUGIN

  57. ./lib/index.js export default function () { return { visitor: {

    TemplateElement({node}) { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; }
  58. export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  59. export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  60. export default function () { return { visitor: { TemplateElement({node})

    { node.value.raw = node.value.raw.replace( /:tada:/g, '\u{1F389}' ); } } }; } ./lib/index.js
  61. github.com/jamiebuilds/babel-handbook

  62. JSX ( just another transform )

  63. babel-plugin-transform-react-jsx const App = () => { return <h1>:tada: Frontend

    Con 2018! :tada:</h1>; };
  64. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement
  65. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement
  66. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement
  67. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement JSXText
  68. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; babel-plugin-transform-react-jsx ReturnStatement JSXElement JSXOpeningElement JSXText JSXClosingElement
  69. const App = () => { return React.createElement( "h1", null,

    "! Frontend Con 2018! !" ); }; const App = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; };
  70. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; const App = () => { return React.createElement( "h1", null, "! Frontend Con 2018! !" ); };
  71. const App = () => { return <h1>:tada: Frontend Con

    2018! :tada:</h1>; }; const App = () => { return React.createElement( "h1", null, "! Frontend Con 2018! !" ); }; REACT?
  72. /* @jsx heroify */ const App = () => {

    return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; babel-plugin-transform-react-jsx
  73. /* @jsx heroify */ const App = () => {

    return heroify( "h1", null, "! Frontend Con 2018! !" ); }; /* @jsx heroify */ const App = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; };
  74. /* @jsx h */ const App = () => {

    return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); };
  75. /* @jsx h */ import {h} from 'preact'; const App

    = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ import {h} from 'preact'; const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); };
  76. /* @jsx h */ import {h} from 'preact'; const App

    = () => { return <h1>:tada: Frontend Con 2018! :tada:</h1>; }; /* @jsx h */ import {h} from 'preact'; const App = () => { return h( "h1", null, "! Frontend Con 2018! !" ); }; JSX is only one option
  77. None
  78. vue-template-explorer.now.sh

  79. vue-template-explorer.now.sh You don't need a complex build setup

  80. <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> </head> <body> </body> </html>
  81. <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> </body> </html>
  82. <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> <script> const { h, render } = window.preact; </script> </body> </html>
  83. <!DOCTYPE html> <html> <head> <title>You don't need JSX</title> <link rel="stylesheet"

    href="https: //contentful.github.io/ .../cf-extension.css"> <script src="https: //unpkg.com/contentful-ui-extensions-sdk@3"> </script> <script> /* Preact 8.2.9 */ !function () { "use strict"; function e() { } function ... </script> </head> <body> <script> const { h, render } = window.preact; render( h('h1', {class: 'headline-1'}, '! Frontend Con 2018! !'), document.body ); </script> </body> </html>
  84. reactjs.org/docs/react-without-jsx.html

  85. github.com/developit/htm

  86. h()?

  87. github.com/hyperhype/hyperscript

  88. const h = (nodeName, attributes, ...children) => {}; ./our-hyperscript.js

  89. const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); return el; }; ./our-hyperscript.js
  90. const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } return el; }; ./our-hyperscript.js
  91. for (let key in attributes) { el.setAttribute(key, attributes[key]); } ./our-hyperscript.js

  92. for (const key of Object.keys(attributes)) { el.setAttribute(key, attributes[key]); } ./our-hyperscript.js

  93. for (const [key, value] of Object.entries(attributes)) { el.setAttribute(key, value); }

    ./our-hyperscript.js
  94. ctfl.io/fun-with-enumeration

  95. const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } return el; }; ./our-hyperscript.js
  96. const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { el.appendChild(child); } }); return el; }; ./our-hyperscript.js
  97. import h from './our-hyperscript.js'; const App = () => {

    return h( "h1", null, "! Frontend Con 2018! !" ); }; console.log('App()', App()); ./app.js
  98. import h from './our-hyperscript.js'; const App = () => {

    return h( "h1", null, "! Frontend Con 2018! !" ); }; console.log('App()', App()); ./app.js
  99. const App = () => { return h( "h1", null,

    "! Frontend Con 2018! !" ); }; ./app.js
  100. const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); }; ./app.js
  101. ./app.js const App = (props) => { const {list} =

    props; return ( <div class="app">, <h1>! Frontend Con 2018! !</h1> <ul> {...list.map(item => <li>{item}</li>)} </ul> </div> ); };
  102. const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ); ); }; ./app.js
  103. const App = (props) => {...}; ./app.js

  104. let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = App(state); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  105. let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; ./app.js
  106. let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = { list: [ '"', '#', '$', '%', '&', ''', '(', ')', '*', '+' ] }; ./app.js
  107. let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = { list: [ '"', '#', '$', '%', '&', ''', '(', ')', '*', '+' ] }; render(state); ./app.js
  108. None
  109. NICELY DECLARATIVE

  110. let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = {...}; render(state); ./app.js
  111. let currentApp; const App = (props) => {...}; const render

    = (state) => {...}; const state = {...}; render(state); setInterval(_ => { state.list = [ ...state.list, getRandomItemFromArray(state.list) ]; render(state); }, 1000); ./app.js
  112. None
  113. A LOT OF WORK TO DO

  114. “We want to render components and have them updated only

    when data changes - that's where the power of vDOM diffing shines.
  115. vWHAT?

  116. Virtual DOM on GitHub

  117. Features Community Performance Which one should I use? DX Documentation

    Ecosystem Stability
  118. Features Community Performance Which one should I use? DX Documentation

    Ecosystem Stability There are always trade-offs...
  119. h()

  120. const h = (nodeName, attributes, ...children) => { const el

    = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } children.forEach(child => { if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { el.appendChild(child); } }) return el; } ./our-vdom.js
  121. ./our-vdom.js const h = (nodeName, attributes, ...children) => { return

    {nodeName, attributes, children}; } h(...) → virtual DOM nodes
  122. const App = (props) => { const {list} = props;

    return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); }; ./app.js
  123. const App = (props) => {...}; ./app.js { "nodeName": "div",

    "attributes": { "class": "app" }, "children": [ { "nodeName": "h1", "attributes": null, "children": ["! Frontend Con 2018! !"] }, { "nodeName": "ul", "attributes": null, "children": [ { "nodeName": "li", "attributes": null, "children": ["""] },
  124. const renderNode = (vnode) => { } ./our-vdom.js

  125. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; } ./our-vdom.js
  126. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); } ./our-vdom.js
  127. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); } ./our-vdom.js
  128. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } } ./our-vdom.js
  129. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  130. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  131. let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = App(state); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  132. let currentApp; const App = (props) => {...}; const render

    = (state) => { const newApp = renderNode(App(state)); if (currentApp) { document.body.replaceChild(newApp, currentApp); } else { document.body.appendChild(newApp); } currentApp = newApp; }; ./app.js
  133. MORE CONTROL

  134. COMPONENT RENDERING

  135. ./app.js const App = ({list}) => { return h('div', {class:

    'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ); ); };
  136. ./app.js class App { constructor () { this.state = {

    list: ['"', '#', '$', ...] }; } render (props, state) { const {list} = state; return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  137. ./app.js class App { constructor () { this.state = {

    list: ['"', '#', '$', ...] }; } render (props, {list}) { return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  138. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); return el; } ./our-vdom.js
  139. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { el = document.createElement(nodeName); for (let key in attributes) { el.setAttribute(key, attributes[key]); } (children || []).forEach(c => el.appendChild(renderNode(c))); } return el; } ./our-vdom.js
  140. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } return el; } ./our-vdom.js
  141. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js
  142. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js overreacted.io/how-does-react-tell-a-class-from-a-function/
  143. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { // ... } return el; } ./our-vdom.js
  144. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js
  145. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js
  146. const renderNode = (vnode) => { let el; const {nodeName,

    attributes, children} = vnode; if (vnode.split) return document.createTextNode(vnode); if (typeof nodeName === 'string') { // ... } else if (typeof nodeName === 'function') { const component = new nodeName(attributes); el = renderNode(component.render(component.props, component.state)); component.base = el; } return el; } ./our-vdom.js A COMPONENT HOLDS A DOM REFERENCE
  147. setState()

  148. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; } render (props, {list}) { return h('div', {class: 'app'}, h('h1', null, '! Frontend Con 2018! !'), h('ul', null, ...list.map(item => h('li', null, item)) ) ); } }
  149. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; } render (props, {list}) {...} }
  150. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } render (props, {list}) {...} }
  151. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  152. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  153. renderComponent() , , , , , ! ! - .

    . / /
  154. const renderComponent = (component) => { }; ./our-vdom.js

  155. const renderComponent = (component) => { const oldBase = component.base;

    }; ./our-vdom.js
  156. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); }; ./our-vdom.js
  157. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  158. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); } COMPONENTS 
 RENDER 
 AUTONOMOUSLY ./our-vdom.js
  159. render() render() setState({...}) renderComponent(this) render() render() document.body APP SIDEBAR WIDGET

    MAIN SLIDER TABS SIDEBAR WIDGET SLIDER TABS
  160. renderComponent(this) render() LIST document.body APP LIST setInterval(...) setState({...})

  161. None
  162. document.body APP ... ... ...

  163. DIFFING

  164. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  165. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; DOM ./our-vdom.js
  166. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; virtual DOM ./our-vdom.js
  167. { nodeName: 'div', attributes: { key: 'value' }, children: [

    'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) DOM virtual DOM ./our-vdom.js
  168. const renderComponent = (component) => { const oldBase = component.base;

    component.base = renderNode( component.render(component.props, component.state) ); oldBase.parentNode.replaceChild(component.base, oldBase); }; ./our-vdom.js
  169. const renderComponent = (component) => { }; ./our-vdom.js

  170. const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); }; ./our-vdom.js
  171. const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); diff(component.base, rendered); }; ./our-vdom.js
  172. const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  173. const diff = (dom, vnode) => { }; ./our-vdom.js

  174. const diff = (dom, vnode) => { const newDom =

    renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; }; ./our-vdom.js
  175. const diff = (dom, vnode) => { const hasNewKids =

    dom.childNodes.length !== vnode.children.length; if (hasNewKids) { } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } }; ./our-vdom.js
  176. const diff = (dom, vnode) => { const hasNewKids =

    dom.childNodes.length !== vnode.children.length; if (hasNewKids) { dom.appendChild( renderNode(vnode.children[vnode.children.length - 1]) ); return dom; } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } }; ./our-vdom.js
  177. None
  178. ONLY TOUCH WHAT CHANGED

  179. DOM { nodeName: 'div', attributes: { key: 'value' }, children:

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

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

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

    [ 'text node' ] } Element( nodeName: 'div', attributes: { getItem('key'), setItem('key', 'value') }, childNodes: [ TextNode('text node') ] ) virtual DOM
  183. ./app.js const diff = (dom, vnode) => { const hasNewKids

    = dom.childNodes.length !== vnode.children.length; if (hasNewKids) { dom.appendChild( renderNode(vnode.children[vnode.children.length - 1]) ); return dom; } else { const newDom = renderNode(vnode); dom.parentNode.replaceChild(newDom, dom); return newDom; } };
  184. ./app.js const diff = (dom, vnode) => { };

  185. ./app.js const diff = (dom, vnode) => { // compare

    type };
  186. ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes };
  187. ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children };
  188. ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children // optimise as much as you can! };
  189. www.youtube.com/watch?v=LY6y3HbDVmg

  190. REDUCE DOM DIFFING

  191. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  192. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.prevState = Object.assign({}, this.state); this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  193. const renderComponent = (component) => { const rendered = component.render(

    component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  194. const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  195. const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  196. const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  197. const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); }; ./our-vdom.js
  198. const renderComponent = (component) => { const {state, props} =

    component; component.state = component.prevState; component.props = component.prevProps; if (component.shouldComponentUpdate(props, state)) { component.props = props; component.state = state; const rendered = component.render( component.props, component.state ); component.base = diff(component.base, rendered); } }; ./our-vdom.js
  199. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) { this.prevState = Object.assign({}, state); this.state = Object.assign(this.state, state); renderComponent(this); } render (props, {list}) {...} }
  200. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } setState(state) {...} render (props, {list}) {...} }
  201. ./app.js class App { constructor () { this.state = {

    list: [ '"', '#', '$', ... ] }; this.timer = setInterval(_ => { this.setState({ list: [...this.state.list, getRandomItemFromArray(state.list)] }); }, 1000); } shouldComponentUpdate(nextProps, nextState) { return this.state.list.length !== nextState.list.length; } setState(state) {...} render (props, {list}) {...} }
  202. SAVE EVEN MORE WORK

  203. parcelRequire=function(e,r,n){var t="function"==typeof parcelRequire&&parcelRequire,i="function"==typeof require&&require;function u(n,o){if(!r[n]){if(!e[n]) {var f="function"==typeof parcelRequire&&parcelRequire;if(!o&&f)return f(n,!0);if(t)return t(n,!0);if(i&&"string"==typeof

    n)return i(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}a.resolve=function(r){return e[n][1][r]||r};var l=r[n]=new u.Module(n);e[n] [0].call(l.exports,a,l,l.exports)}return r[n].exports;function a(e){return u(a.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e) {this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=t;for(var o=0;o<n.length;o++)u(n[o]);return u}({4:[function(require,module,exports) { var e=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=! 0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function t(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function n(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function r(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:! 1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function o(e,t){if(!(e instanceof t))throw new T y p e E r r o r ( " C a n n o t c a l l a c l a s s a s a f u n c t i o n " ) } v a r i = f u n c t i o n ( e , t ) { f o r ( v a r n = a r g u m e n t s . l e n g t h , r = A r r a y ( n > 2 ? n - 2 : 0 ) , o = 2 ; o < n ; o + +)r[o-2]=arguments[o];return{nodeName:e,attributes:t,children:r}},a=function(e){var t=e.base,n=e.render(e.props,e.state),r=u(t,n);e.base=r},u=function e(t,n,r) {if(t){if("string"==typeof n)return t.nodeValue=n,t;if("function"==typeof n.nodeName){var o=new n.nodeName(n.attributes);return e(t,o.render(o.props,o.state)),t}t.childNodes.length!==n.children.length&&t.appendChild(f(n.children[n.children.length-1]));for(var i=0;i<n.children.length;i+ +)e(t.childNodes[i],n.children[i]);return t}var a=f(n);return r.appendChild(a),a},s=function(){function t(e){o(this,t),this.props=e,this.state={}}return e(t, [{key:"setState",value:function(e){this.state=Object.assign(this.state,e),a(this)}}]),t}(),l=function(a){function u(e){o(this,u);var r=n(this,(u.__proto__|| Object.getPrototypeOf(u)).call(this,e));return r.state.list=["" ","# ","$ ","% ","& ","' ","( ",") ","* ","+ "],r.timer=setInterval(function() {r.setState({list:[].concat(t(r.state.list),[r.state.list[Math.floor(Math.random()*r.state.list.length)]])})},1e3),r}return r(u,s),e(u, [{key:"render",value:function(e,t){var n=t.list;return i("div",{class:"app"},i("h1",null,"! JSHeroes 2018! ! "),i(c,{list:n}))}}]),u}(),c=function(a){function u(){return o(this,u),n(this,(u.__proto__||Object.getPrototypeOf(u)).apply(this,arguments))}return r(u,s),e(u,[{key:"render",value:function(e,n){return i . a p p l y ( v o i d 0 , [ " u l " , n u l l ] . c o n c a t ( t ( e . l i s t . m a p ( f u n c t i o n ( e ) { r e t u r n i ( " l i " , n u l l , e ) } ) ) ) ) } } ] ) , u } ( ) , f = f u n c t i o n e ( t ) { v a r n = v o i d 0 , r = t . n o d e N a m e , o = t . a t t r i b u t e s , i = t . c h i l d r e n ; i f ( t . s p l i t ) r e t u r n d o c u m e n t . c r e a t e T e x t N o d e ( t ) ; i f ( " s t r i n g " = = t y p e o f r ) f o r ( k e y i n n = d o c u m e n t . c r e a t e E l e m e n t ( r ) , o ) " o " = = k e y [ 0 ] & & " n " = = k e y [ 1 ] ? n . a d d E v e n t L i s t e n e r ( k e y . t o L o w e r C a s e ( ) . s u b s t r i n g ( 2 ) , o [ k e y ] ) : " c l a s s " = = = k e y ? n.className=o[key]:n.setAttribute(key,o[key]);else if("function"==typeof r){var a=new r(o);n=e(a.render(a.props,a.state)),a.base=n}return(i|| []).forEach(function(t){return n.appendChild(e(t))}),n},p=function(e,t){u(void 0,e,t)};p(i(l),document.body); },{}]},{},[4]) app.min.js vDOM example on Codepen
  204. preact.min.js

  205. BENEFITS

  206. DECLARATIVE UI

  207. export default class App extends Component { ... render(props, {

    highlighted }) { return ( <button type="button" highlighted={highlighted} onclick={this.toggleHighlight}> { highlighted ? 'Please stop!': 'Highlight me!' } </button> ); } } codesandbox.io/s/rwjol2n3op
  208. codesandbox.io/s/rwjol2n3op export default class App extends Component { ... render(props,

    { highlighted }) { return React.createElement( 'button', { type: 'button', highlighted: highlighted, onclick: this.toggleHighlight }, highlighted ? 'Please stop!' : 'Highlight me!' ); } }
  209. codesandbox.io/s/rwjol2n3op export default class App extends Component { ... render(props,

    { highlighted }) { return React.createElement( 'button', { type: 'button', highlighted: highlighted, onclick: this.toggleHighlight }, highlighted ? 'Please stop!' : 'Highlight me!' ); } } It's "just" function calls
  210. SMART AND SELECTIVE
 RENDERING

  211. render() render() setState({...}) renderComponent(this) render() render() document.body APP SIDEBAR WIDGET

    MAIN SLIDER TABS SIDEBAR WIDGET SLIDER TABS
  212. LIMITED DOM OPERATIONS

  213. ./app.js const diff = (dom, vnode) => { // compare

    type // compare attributes // compare children }
  214. function enqueueRender(component) { // ... }

  215. BUT IS THE vDOM THE
 SILVER BULLET?

  216. github.com/Polymer/lit-html github.com/WebReflection/hyperHTML

  217. import { html, render } from 'lit-html'; const data =

    { list: ['"', ..., '+'] };
  218. import { html, render } from 'lit-html'; const data =

    { list: ['"', ..., '+'] }; const App = ({list}) => { return html` <div class="app"> <h1>! Frontend Con 2018! !</h1> <ul> ${ list.map(item => html`<li>${ item }</li>`) } </ul> </div> ` };
  219. import { html, render } from 'lit-html'; const data =

    { list: ['"', ..., '+'] }; const App = ({list}) => { return html` <div class="app"> <h1>! Frontend Con 2018! !</h1> <ul> ${ list.map(item => html`<li>${ item }</li>`) } </ul> </div> ` }; render(App(data), document.body);
  220. html`<li>${ item }</li>`

  221. html`<li>${ '"' }</li>` function html(...args) { console.log(args); } [['<li>', '</li>'],

    '"']
  222. html`<li>${ '"' }</li>` function html(...args) { console.log(args); } [['<li>', '</li>'],

    '"']
  223. html`<li>${ '"' }</li>` function html(...args) { console.log(args); } [['<li>', '</li>'],

    '"']
  224. html`<li>${ '"' }</li>` function html(...args) { console.log(args); } [['<li>', '</li>'],

    '"']
  225. html`<li>${ '"' } ${ Math.random() }</li>` function html(...args) { console.log(args);

    } [['<li>', ' ', '</li>' ], '"', 0.62...]
  226. html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]
  227. html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]
  228. html`<li>${ '"' } ${ Math.random() }</li>` function html(chunks, ...interpolations) {

    // diff only things that can change } [['<li>', ' ', '</li>' ], '"', 0.62...]
  229. Only certain nodes can change!

  230. A DIFFERENT APPROACH

  231. NEW POSSIBILITIES

  232. codepen.io/WebReflection/pen/JObreJ?editors=0010

  233. I READ A LOT OF PREACT CODE

  234. YOU'LL NEVER GET IT!

  235. SLEEPLESS NIGHTS

  236. A LOT OF BREAKS

  237. AND THEN
 IT MADE SENSE

  238. THERE IS NO MAGIC IN CODE!

  239. THANKS FOR LISTENING SLIDES: ctfl.io/what-the-vdom
 @stefanjudis