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

WorkerDOM: JavaScript Concurrency and the DOM

Malte Ubl
August 21, 2018

WorkerDOM: JavaScript Concurrency and the DOM

Malte Ubl

August 21, 2018
Tweet

Other Decks in Programming

Transcript

  1. JavaScript,
    Concurrency,
    and the DOM
    Malte Ubl and Kristofer Baxter

    View full-size slide

  2. Concurrency is a technical word for multiple parts of a computer
    program running at the same time.
    If your computer/phone has more than one core (it does) or you have
    more than one computer (the cloud does) then this can make things get
    done much faster.
    It generally makes things more complicated

    View full-size slide

  3. Threads
    Programs that run
    on a computer
    Photo: D-BASE, Getty Images, DigitalVision

    View full-size slide

  4. Single-threaded
    Application has only 

    one thread
    Photo: D-BASE, Getty Images, DigitalVision

    View full-size slide

  5. Traditionally web browsers have only a single thread.
    The main thread.
    Your JavaScript runs on that thread.
    If your JavaScript runs and until it is done running, your browser cannot
    do anything else.

    View full-size slide

  6. OMG, 60 frames 

    per second

    View full-size slide

  7. OMG, 60 frames 

    per second
    JANK

    View full-size slide

  8. You have 16ms to
    update the screen,
    right?

    View full-size slide

  9. You, layout, style
    recalc, paint,
    composite, actual
    physical pixel flip

    View full-size slide

  10. 0 16
    Browser
    You: Developer JavaScript
    8

    View full-size slide

  11. 0 8
    You: Developer JavaScript
    Event
    Processing
    Determine New
    State
    State Change Processing Issue DOM Updates
    click

    View full-size slide

  12. 0 10
    Developer JavaScript
    Event
    Processing
    Determine New
    State
    State Change Processing Issue DOM Updates
    click

    View full-size slide

  13. 0 18
    Browser
    You: Developer JavaScript
    10
    JANK

    View full-size slide

  14. It’s common to
    exceed your budget.
    Don’t feel bad! We all do it!

    View full-size slide

  15. Not All Mobile Devices
    Are Created Equal

    View full-size slide

  16. 0
    100
    Brand New Devices
    Categorized by Price Segments

    View full-size slide

  17. 15th Percentile ($80)
    75th Percentile ($600)
    90th Percentile ($1000)
    0
    100

    View full-size slide

  18. 0
    1000
    2000
    3000
    4000
    5000
    2011 2013 2016 2018
    Mobile Device Single Core Performance Over Time
    iPhone X
    Pixel
    Nokia 2
    Samsung J3
    Remember the
    Performance Gap

    View full-size slide

  19. What’s Multicore
    Performance Like?

    View full-size slide

  20. 0
    1000
    2000
    3000
    4000
    5000
    2011 2013 2016 2018
    Low End Devices Multi Core Performance Over Time
    Samsung J3
    Nokia 2

    View full-size slide

  21. Seriously, it can’t
    be that bad right?
    Oh, it is.

    View full-size slide

  22. Video Showing Ares-6 Benchmark on iPhone X versus Nokia 2

    View full-size slide

  23. OK, we need some
    concurrency in our
    JavaScript

    View full-size slide

  24. Introducing a
    super amazing new
    API … Web Workers!
    Photo: JStevensJr, Getty Images, Creatas Video+ / Getty Images Plus

    View full-size slide

  25. Web Workers
    A way to do multi-threading in JavaScript in web browsers.
    Workers share no state with each other or the main-thread.
    Workers only have access to a very limited set of web APIs.
    In particular, they do not have access to the DOM

    View full-size slide

  26. const worker = new Worker('worker.js');
    const inputEl = document.createElement(‘input');
    inputEl.onchange = _ => {
    worker.postMessage({value: inputEl.value});
    console.log(`value, ${inputEl.value} passed to worker`);
    };
    document.body.appendChild(inputEl);

    View full-size slide

  27. const worker = new Worker('worker.js');
    const inputEl = document.createElement(‘input');
    inputEl.onchange = _ => {
    worker.postMessage({value: inputEl.value});
    console.log(`value, ${inputEl.value} passed to worker`);
    };
    document.body.appendChild(inputEl);
    worker.onmessage = e => {
    console.log(`result, ${e.data} received from worker thread’);
    };
    Main Thread
    self.onmessage = e => {
    console.log(`value, ${e.data} received from main thread`);
    const result = `Result: ${e.data.value}`;
    console.log(`result, ${result} passed to main thread`);
    postMessage(result);
    }
    Worker Thread
    change postMessage
    onMessage
    onMessage
    postMessage

    View full-size slide

  28. Low Level Primitive
    Is there something a bit more abstracted, perhaps usable?

    View full-size slide


  29. <br/>(async function() {<br/>class MyRemoteClass {<br/>doExpensiveCalculation(a, b) {<br/>return a + b;<br/>}<br/>}<br/>const instance = await Clooney.spawn(MyRemoteClass);<br/>console.log(await instance.doExpensiveCalculation(5, 23));<br/>})();<br/>
    https://github.com/GoogleChromeLabs/clooney

    View full-size slide

  30. const endpoint = 'https://endpoint.com/dogs/are/best”;
    const result = await worker{|
    const res = await fetch(endpoint);
    const json = await res.json();
    return json[2].firstName;
    |};
    https://github.com/domenic/proposal-blocks

    View full-size slide

  31. https://github.com/developit/stockroom

    View full-size slide

  32. I thought this talk
    was about the DOM?
    Yes, you’re right of course.

    View full-size slide

  33. WorkerDOM
    The same DOM API and Frameworks you know, but in a Web Worker.

    View full-size slide

  34. Original Requirements
    “Hey Kris, make it so that React works in a Web Worker.”
    That is actually the full list.
    But then we realized we could be a bit more abstract.

    View full-size slide

  35. Makes the full[1] DOM available to your Web Worker.
    Effectively this means you can run your existing web app in a Web
    Worker.
    And It Just Works™ [1]
    [1] Terms and Conditions apply.

    View full-size slide

  36. Use Cases
    Speed up your existing app.
    Support framework authors in speeding up their existing framework.
    Isolate performance impact of third party scripts on your app.

    View full-size slide

  37. Event
    Processing
    Determine
    New State
    State Change
    Processing
    Issue DOM
    Updates
    10
    Browser
    18
    Main
    Thread
    Time on main thread: 18ms / janky
    Time until event processed: 18ms

    View full-size slide

  38. Event
    Processing
    Determine
    New State
    State Change
    Processing
    Issue DOM
    Updates
    2
    Browser
    10
    Main
    Thread
    Background
    Thread
    Time on main thread: 10ms / silky smooth
    Time until event processed: 18ms (latency unchanged)

    View full-size slide

  39. Limitations
    Some DOM APIs aren’t yet implemented.
    A small number of DOM APIs cannot be implemented and alternatives
    are provided.
    A smaller number of DOM APIs cannot be implemented and no
    alternative can be provided.

    View full-size slide

  40. Unimplementable with alternative
    Primarily DOM APIs that provide synchronous access to computed
    layout.
    Think getBoundingClientRect and getComputedStyle
    Alternatives are provided with Async suffix like
    getBoundingClientRectAsync which return a promise.
    Side benefit: This automatically prevents performance red flags like
    “double sync layout in a single frame”.

    View full-size slide

  41. Unimplementable without alternative
    Synchronous methods on Event like preventDefault and stopPropagation.
    Transferable events may come to browsers soon which would make this
    work.
    Until then: If you need these methods, you may need to run them
    manually on the main thread.

    View full-size slide

  42. How does this work?
    No need to understand the guts to use it, but it is fun stuff.

    View full-size slide

  43. Main Thread Worker Thread
    UpgradedElement
    HTMLDivElement
    WorkerDOM
    Document
    HTMLDivElement
    Runtime
    Hydration
    Mutation
    Upgrade Element
    Click Handler
    Mouse Handler

    View full-size slide

  44. Hydration
    Compare and Upgrade String Markup Into Usable DOM Nodes
    Photo: Claudia Miranda / EyeEm, Getty Images, EyeEm

    View full-size slide




  45. spaner
    Hello World!

    HTMLSectionElement
    HTMLDivElement
    Text
    HTMLSpanElement
    Text
    HTMLInputElement

    View full-size slide




  46. spaner
    Hello World!


    HTMLSectionElement
    HTMLDivElement
    Text
    HTMLSpanElement
    Text
    HTMLInputElement

    View full-size slide

  47. export interface HydrateableNode {
    readonly nodeType: NodeType; // NodeType.ELEMENT_NODE
    readonly nodeName: string; // "div"
    readonly _index_: number; // 3
    readonly transferred: boolean; // false
    readonly attributes?: Array; // []
    readonly properties?: Array; // []
    readonly childNodes?: Array; // [...]
    readonly textContent?: string; // undefined
    readonly namespaceURI?: string; // undefined
    }



    spaner
    Hello World!


    HydrateableNode
    HydrateableNode
    HydrateableNode
    HydrateableNode
    HydrateableNode
    HydrateableNode
    export interface HydrateableNode {
    readonly nodeType: NodeType;
    readonly nodeName: string;
    readonly _index_: number;
    readonly transferred: boolean;
    readonly attributes?: Array;
    readonly properties?: Array;
    readonly childNodes?: Array;
    readonly textContent?: string;
    readonly namespaceURI?: string;
    }
    HTMLSectionElement
    HTMLDivElement
    Text
    HTMLSpanElement
    Text
    HTMLInputElement

    View full-size slide

  48. {
    "nodeType": 1,
    "nodeName": "section",
    "childNodes": [
    {
    "nodeType": 1,
    "nodeName": "div",
    "childNodes": [...],
    "_index_": 5,
    "transferred": false
    }
    ],
    "_index_": 4,
    "transferred": false,
    }
    [“nodeType”, “nodeName”, “childNodes”, “_index_”, “transferred”] => [0, 1, 4, 7, 8]

    View full-size slide

  49. {
    "nodeType": 1,
    "nodeName": "section",
    "childNodes": [
    {
    "nodeType": 1,
    "nodeName": "div",
    "childNodes": [...],
    "_index_": 5,
    "transferred": false
    }
    ],
    "_index_": 4,
    "transferred": false,
    }
    {
    0: 1,
    1: "section",
    4: [
    {
    0: 1,
    1: "div",
    4: [...],
    7: 5,
    8: false
    }
    ],
    7: 4,
    8: false,
    }
    [“nodeType”, “nodeName”, “childNodes”, “_index_”, “transferred”] => [0, 1, 4, 7, 8]

    View full-size slide

  50. {
    "nodeType": 1,
    "nodeName": "section",
    "childNodes": [
    {
    "nodeType": 1,
    "nodeName": "div",
    "childNodes": [...],
    "_index_": 5,
    "transferred": false
    }
    ],
    "_index_": 4,
    "transferred": false,
    }
    [false, true] => [0, 1]
    {
    0: 1,
    1: "section",
    4: [
    {
    0: 1,
    1: "div",
    4: [...],
    7: 5,
    8: 0
    }
    ],
    7: 4,
    8: 0,
    }

    View full-size slide

  51. {
    "nodeType": 1,
    "nodeName": "section",
    "childNodes": [
    {
    "nodeType": 1,
    "nodeName": "div",
    "childNodes": [...],
    "_index_": 5,
    "transferred": false
    }
    ],
    "_index_": 4,
    "transferred": false,
    }
    {
    0: 1,
    1: 1,
    4: [
    {
    0: 1,
    1: 2,
    4: [...],
    7: 5,
    8: 0
    }
    ],
    7: 4,
    8: 0,
    }
    [“section”, “div”] => [1, 2]

    View full-size slide

  52. export interface HydrationFromWorker {
    readonly type: MessageType.HYDRATE;
    readonly strings: Array;
    readonly nodes: HydrateableNode;
    readonly addedEvents: Array;
    }
    {
    9: 2,
    39: ["#document", "body", "div", "#text", "Hello
    World!", …],
    35: {…},
    20: [],
    }

    View full-size slide

  53. Mutation
    A Static Document Would be Quite Boring

    View full-size slide

  54. const span = document.createElement('span');
    span.addEventListener('click', (e) => {
    span.classList.toggle('clicked');
    div.style.color = div.style.color === “green”
    ? "red" : "green";
    });

    View full-size slide

  55. Does it actually work?
    Demo Time

    View full-size slide

  56. Can I try this?
    worker-dom became available on GitHub and npm a few minutes ago.
    It is still very alpha.
    If you maintain a framework or build tool, we’d love to work with you to
    make sure it works with WorkerDOM!

    View full-size slide

  57. Why we are building this
    WorkerDOM will allow authors of AMP documents to run their own
    JavaScript.
    WorkerDOM is alpha today, we will make it rock-solid.

    View full-size slide

  58. Thank you!
    GitHub: bit.ly/worker-dom
    Blog post: bit.ly/worker-dom-blog

    @kristoferbaxter & @cramforce

    View full-size slide