Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What the v...DOM?

What the v...DOM?

stefan judis

April 18, 2018
Tweet

More Decks by stefan judis

Other Decks in Technology

Transcript

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

    View Slide

  2. STEFAN JUDIS
    @stefanjudis
    www.stefanjudis.com
    [email protected]

    View Slide

  3. 2013

    View Slide

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

    and JSX."
    MAGIC!

    View Slide

  5. THERE IS NO
    MAGIC IN CODE!

    View Slide

  6. SOMETIMES

    IT FEELS 

    LIKE MAGIC

    View Slide

  7. TAKE A BREAK!

    View Slide

  8. Matthias

    View Slide

  9. IMPERATIVE

    View Slide

  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.

    View Slide

  11. The imperative programming
    paradigm uses statements 

    that change a program's state.

    View Slide

  12. DECLARATIVE

    View Slide

  13. My address is Berlin,
    Friedrichshain.

    View Slide


  14. The declarative programming
    paradigm expresses the logic of a
    computation without describing
    its control flow.

    View Slide

  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);
    }

    View Slide

  16. INTERFACES

    View Slide

  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

    View Slide

  18. Declarative UI
    type="button"
    highlighted={highlighted}
    onclick={this.toggleHighlight}>
    { highlighted ? 'Please stop!': 'Highlight me!' }

    View Slide

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

    View Slide

  20. export default class App extends Component {
    ...
    render(props, { highlighted }) {
    return (
    type="button"
    highlighted={highlighted}
    onclick={this.toggleHighlight}>
    { highlighted ? 'Please stop!': 'Highlight me!' }

    );
    }
    }
    codesandbox.io/s/rwjol2n3op

    View Slide

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

    );
    }
    }
    codesandbox.io/s/rwjol2n3op
    THAT'S NOT
    JAVASCRIPT!?

    View Slide

  22. CODE
    TRANSFORMS

    View Slide

  23. !
    &
    ?
    PARSE
    !
    ~ ?
    TRANSFORM
    GENERATE

    View Slide

  24. ./example/input.js
    const year = (new Date()).getFullYear();
    const msg = `:tada: Frontend Con ${year}! :tada:`;
    console.log(msg);

    View Slide

  25. View Slide

  26. babeljs.io

    View Slide

  27. PARSE
    with
    Babylon

    View Slide

  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

    View Slide

  29. AST
    ( Abstract Syntax Tree )

    View Slide

  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.

    View Slide

  31. View Slide

  32. {
    "type": "Program",
    "start": 0,
    "end": 105,
    "loc": {
    "start": {
    "line": 1,
    "column": 0
    },
    "end": {
    "line": 3,
    "column": 17
    }
    }
    }

    View Slide

  33. {
    "type": "VariableDeclaration",
    "start": 0,
    "end": 40,
    "loc": {
    "start": {
    "line": 1,
    "column": 0
    },
    "end": {
    "line": 1,
    "column": 40
    }
    },
    "declarations": [...],
    "kind": "const"
    }

    View Slide

  34. {
    "type": "ExpressionStatement",
    "start": 88,
    "end": 105,
    "loc": {
    "start": {
    "line": 3,
    "column": 0
    },
    "end": {
    "line": 3,
    "column": 17
    }
    },
    "expression": {...}
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. astexplorer.net

    View Slide

  44. ASTs are
    everywhere

    View Slide

  45. astexplorer.net

    View Slide

  46. ctfl.io/asts-in-graphql

    View Slide

  47. TRANSFORM
    with
    @babel/traverse

    View Slide

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

    View Slide

  49. const {parse} = require('babylon');
    const code = `...`;
    const ast = parse(code);
    ./my-own-code-transform/index.js

    View Slide

  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

    View Slide

  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

    View Slide

  52. GENERATE
    with
    @babel/generator

    View Slide

  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

    View Slide

  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);

    View Slide

  55. View Slide

  56. BABEL
    PLUGIN

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. github.com/jamiebuilds/babel-handbook

    View Slide

  62. JSX
    ( just another transform )

    View Slide

  63. babel-plugin-transform-react-jsx
    const App = () => {
    return :tada: Frontend Con 2018! :tada:;
    };

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  77. View Slide

  78. vue-template-explorer.now.sh

    View Slide

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

    View Slide




  80. You don't need JSX






    View Slide




  81. You don't need JSX


    <br/>/* Preact 8.2.9 */<br/>!function () { "use strict"; function e() { } function ...<br/>




    View Slide




  82. You don't need JSX


    <br/>/* Preact 8.2.9 */<br/>!function () { "use strict"; function e() { } function ...<br/>


    <br/>const { h, render } = window.preact;<br/>


    View Slide




  83. You don't need JSX


    <br/>/* Preact 8.2.9 */<br/>!function () { "use strict"; function e() { } function ...<br/>


    <br/>const { h, render } = window.preact;<br/>render(<br/>h('h1', {class: 'headline-1'}, '! Frontend Con 2018! !'),<br/>document.body<br/>);<br/>


    View Slide

  84. reactjs.org/docs/react-without-jsx.html

    View Slide

  85. github.com/developit/htm

    View Slide

  86. h()?

    View Slide

  87. github.com/hyperhype/hyperscript

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  94. ctfl.io/fun-with-enumeration

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  101. ./app.js
    const App = (props) => {
    const {list} = props;
    return (
    ,
    ! Frontend Con 2018! !

    {...list.map(item => {item})}


    );
    };

    View Slide

  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

    View Slide

  103. const App = (props) => {...};
    ./app.js

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  108. View Slide

  109. NICELY
    DECLARATIVE

    View Slide

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

    View Slide

  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

    View Slide

  112. View Slide

  113. A LOT OF
    WORK TO DO

    View Slide

  114. “We want to render components
    and have them updated
    only when data changes
    - that's where the power of
    vDOM diffing shines.

    View Slide

  115. vWHAT?

    View Slide

  116. Virtual DOM on GitHub

    View Slide

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

    View Slide

  118. Features Community
    Performance
    Which one should I use?
    DX
    Documentation
    Ecosystem
    Stability
    There are always
    trade-offs...

    View Slide

  119. h()

    View Slide

  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

    View Slide

  121. ./our-vdom.js
    const h = (nodeName, attributes, ...children) => {
    return {nodeName, attributes, children};
    }
    h(...) → virtual DOM nodes

    View Slide

  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

    View Slide

  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": ["""]
    },

    View Slide

  124. const renderNode = (vnode) => {
    }
    ./our-vdom.js

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  133. MORE CONTROL

    View Slide

  134. COMPONENT
    RENDERING

    View Slide

  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))
    );
    );
    };

    View Slide

  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))
    )
    );
    }
    }

    View Slide

  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))
    )
    );
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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/

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  147. setState()

    View Slide

  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))
    )
    );
    }
    }

    View Slide

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

    View Slide

  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}) {...}
    }

    View Slide

  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}) {...}
    }

    View Slide

  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}) {...}
    }

    View Slide

  153. renderComponent()
    ,
    ,
    ,
    ,
    , !
    !
    -
    . .
    /
    /

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  160. renderComponent(this)
    render()
    LIST
    document.body
    APP
    LIST
    setInterval(...)
    setState({...})

    View Slide

  161. View Slide

  162. document.body
    APP
    ... ... ...

    View Slide

  163. DIFFING

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  173. const diff = (dom, vnode) => {
    };
    ./our-vdom.js

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  177. View Slide

  178. ONLY TOUCH
    WHAT CHANGED

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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;
    }
    };

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  188. ./app.js
    const diff = (dom, vnode) => {
    // compare type
    // compare attributes
    // compare children
    // optimise as much as you can!
    };

    View Slide

  189. www.youtube.com/watch?v=LY6y3HbDVmg

    View Slide

  190. REDUCE DOM
    DIFFING

    View Slide

  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}) {...}
    }

    View Slide

  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}) {...}
    }

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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}) {...}
    }

    View Slide

  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}) {...}
    }

    View Slide

  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}) {...}
    }

    View Slide

  202. SAVE EVEN
    MORE WORK

    View Slide

  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{ var e=function(){function e(e,t){for(var n=0;n0),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);tsuper() 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+)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

    View Slide

  204. preact.min.js

    View Slide

  205. BENEFITS

    View Slide

  206. DECLARATIVE UI

    View Slide

  207. export default class App extends Component {
    ...
    render(props, { highlighted }) {
    return (
    type="button"
    highlighted={highlighted}
    onclick={this.toggleHighlight}>
    { highlighted ? 'Please stop!': 'Highlight me!' }

    );
    }
    }
    codesandbox.io/s/rwjol2n3op

    View Slide

  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!'
    );
    }
    }

    View Slide

  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

    View Slide

  210. SMART AND
    SELECTIVE

    RENDERING

    View Slide

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

    View Slide

  212. LIMITED DOM
    OPERATIONS

    View Slide

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

    View Slide

  214. function enqueueRender(component) {
    // ...
    }

    View Slide

  215. BUT IS THE
    vDOM THE

    SILVER BULLET?

    View Slide

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

    View Slide

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

    View Slide

  218. import { html, render } from 'lit-html';
    const data = { list: ['"', ..., '+'] };
    const App = ({list}) => {
    return html`

    ! Frontend Con 2018! !

    ${ list.map(item => html`${ item }`) }


    `
    };

    View Slide

  219. import { html, render } from 'lit-html';
    const data = { list: ['"', ..., '+'] };
    const App = ({list}) => {
    return html`

    ! Frontend Con 2018! !

    ${ list.map(item => html`${ item }`) }


    `
    };
    render(App(data), document.body);

    View Slide

  220. html`${ item }`

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  225. html`${ '"' } ${ Math.random() }`
    function html(...args) {
    console.log(args);
    }
    [['', ' ', '' ], '"', 0.62...]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  229. Only certain nodes can change!

    View Slide

  230. A DIFFERENT
    APPROACH

    View Slide

  231. NEW
    POSSIBILITIES

    View Slide

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

    View Slide

  233. I READ A LOT OF
    PREACT CODE

    View Slide

  234. YOU'LL
    NEVER GET IT!

    View Slide

  235. SLEEPLESS
    NIGHTS

    View Slide

  236. A LOT OF
    BREAKS

    View Slide

  237. AND THEN

    IT MADE SENSE

    View Slide

  238. THERE IS NO
    MAGIC IN CODE!

    View Slide

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

    @stefanjudis

    View Slide