$30 off During Our Annual Pro Sale. View Details »

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 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 Slide

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

    View Slide

  4. Single-threaded
    Application has only 

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

    View 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 Slide

  6. OMG, 60 frames 

    per second

    View Slide

  7. OMG, 60 frames 

    per second
    JANK

    View Slide

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

    View Slide

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

    View Slide

  10. 0 16
    Browser
    You: Developer JavaScript
    8

    View Slide

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

    View Slide

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

    View Slide

  13. 0 18
    Browser
    You: Developer JavaScript
    10
    JANK

    View Slide

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

    View Slide

  15. Why?

    View Slide

  16. Not All Mobile Devices
    Are Created Equal

    View Slide

  17. View Slide

  18. 0
    100
    Brand New Devices
    Categorized by Price Segments

    View Slide

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

    View Slide

  20. 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 Slide

  21. What’s Multicore
    Performance Like?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. OK, we need some
    concurrency in our
    JavaScript

    View Slide

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

    View Slide

  27. View Slide

  28. 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 Slide

  29. 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 Slide

  30. 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 Slide

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

    View Slide


  32. <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 Slide

  33. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. 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 Slide

  38. 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 Slide

  39. 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 Slide

  40. 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 Slide

  41. 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 Slide

  42. 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 Slide

  43. 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 Slide

  44. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide




  48. spaner
    Hello World!

    HTMLSectionElement
    HTMLDivElement
    Text
    HTMLSpanElement
    Text
    HTMLInputElement

    View Slide




  49. spaner
    Hello World!


    HTMLSectionElement
    HTMLDivElement
    Text
    HTMLSpanElement
    Text
    HTMLInputElement

    View Slide

  50. 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 Slide

  51. {
    "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 Slide

  52. {
    "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 Slide

  53. {
    "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 Slide

  54. {
    "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 Slide

  55. 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 Slide

  56. Mutation
    A Static Document Would be Quite Boring

    View Slide

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

    View Slide

  58. View Slide

  59. Does it actually work?
    Demo Time

    View Slide

  60. 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 Slide

  61. 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 Slide

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

    @kristoferbaxter & @cramforce

    View Slide