Would it be possible to offload some work, e.g. the render function, to a WebWorker?


#1

We have an app that currently takes around 500ms to render, but on older machines can take a lot longer. I’m wondering if it’s possible to cut that time by offloading rendering to a WebWorker.

I think the main issue is that WebWorkers are, by their nature, asynchronous - can the render function return a promise? Could this functionality be added by a plugin?

Can you think of other ways that some JS work can be offloaded to a WebWorker?


Spinner for long v-for rendering
#2

Can you provide a bit more insight into what you think is causing things to render slow?

I think it is unlikely that the solution is to implement something using a web worker. I’m a bit curious as to what kind of stuff you’re doing before the page loads. A slow network request? Parsing a massive json file?


#3

I’m just talking about the Vue init time, not response or parsing. So, looking at the timeline, the Vue._init function takes around 400-500ms, in production mode.

To clarify, I don’t think there’s an issue with Vue as such, just that rendering the whole app in JS is bound to be slow. If some of this work can be done in a separate thread then it would be worth us looking at.


#4

It’s an interesting topic. I was thinking about using WebWorkers with Vue as well!

We have an app that currently takes around 500ms to render (…) can the render function return a promise? (…)

First of all - are you really sure that the render function makes your app slow? Vue is pretty fast in rendering, so I would suspect that there’s something else that makes it slow. For example, some kind of complicated data-parsing, etc that happens before render. It would be good to look at the Chrome dev tools and the Timeline tab, to check what’s really taking so many resources.

Can you think of other ways that some JS work can be offloaded to a WebWorker?

WebWorker will not make your tasks run faster. It will just make them asynchronous. So, when a function execution takes 500ms it will take ~500ms as well when it runs inside a WebWorker thread. So if your application can do something in the meanwhile (when it waits for a WebWorker response), then using a WebWorker makes sense. But when the data that WebWorker is supposed to return is required by the application from the very early stage of the application execution (for example when this is an initial data that drives the template), then using a WebWorker doesn’t make a lot of sense.

Answering your question: Yes, I think that some JS work can be offloaded to a WebWorker. But I wouldn’t rather put a whole instance of a Vue component inside a worker. I would rather move some computation-heavy utility functions to a WebWorkers - for example, functions that iterate over huge arrays/objects and transform these arrays/objects - because this kind of stuff blocks the UI. When a computation is in progress then a browser becomes unresponsive. So if computations were moved to a WebWorker then these computation would not affect the UI performance.


See the WebWorker demo on this site. The demo explains better than me how WebWorker improves the UI performance.


#5

Thanks very much for the reply. I understand about the speed taking the same time, what I was thinking was to split the work up so that some happened in parallel, e.g. if I have two instances of the same component and one is in a separate thread, the overall time should be halved (obviously not that simple, but you get the idea).

Out of interest, why would you not put a whole instance of a component inside a worker? What would be the drawback?

Regarding our app, it’s not the render function that’s taking all that time, for sure - rather the whole Vue init process. I like your idea about moving various utility functions to WebWorkers, I’ll look into that.

Thanks again - very useful reply.


#6

I was thinking was to split the work up so that some happened in parallel

So in that case using a worker seems to be a good idea.

Out of interest, why would you not put a whole instance of a component inside a worker? What would be the drawback?

Correct me if I’m wrong, but I’m afraid that code can’t be shared between a “regular application” and a worker. So each worker would have to include it’s own copy of the Vue library, (and other libraries too). But I might be wrong. You have to check it on your own. If I’m wrong that It might be quite a good idea to run Vue instances inside workers!

Regarding our app, it’s not the render function that’s taking all that time, for sure - rather the whole Vue init process. I like your idea about moving various utility functions to WebWorkers, I’ll look into that.

Here’s a very basic implementation of Vue <=> WebWorker communication:

<div id="app">
    <ul>
        <li v-for="item in myDataTransformedByWorker">
            {{item}}
        </li>
    </ul>
</div>

<script>
    new Vue({
        el: '#app',
        data () {
            return {
                myData: ['1st item', '2nd item', '3rd item'], // Original data
                myDataTransformedByWorker: [] // Data transformed by the worker
            }
        },
        created() {
            // Set up a worker
            const worker = new Worker('worker.js');

            // Listen to the worker `message` event
                worker.addEventListener('message', (e) => {
                    this.myDataTransformedByWorker = e.data;
                });

                // Send the data to the worker
                worker.postMessage(this.myData);
            }
        });
</script>
// worker.js

self.addEventListener('message', (e) => {

    // The original data sent by the Vue
        const data = e.data;

        const dataTransformer = data.map(item => {
            // ...
            // Do some expensive data transformation here.
            // ...
            return `${item} modified by the worker`;
        });

        // Send the transformed data back to the Vue app
        self.postMessage(dataTransformer);

});

#7

Why not move most of the app, including virtualdom, into a worker, and only pass messages back to update the physical dom or forward to handle events? There’s no good reason to do anything on the main thread, it would even be better for battery life as the update would happen faster. The cycles wasted passing the message don’t matter as much as that.

SharedWorkers could further improve this by for instance having one Vuex store for all pages:

image

(Imagine every “Page” is a (dedicated) web worker for diffing, and their tab just applies the results on the next animation frame)


#8

The reason is probably that it would require a 80% rewrite.

Also, many components, plugins and so on need access to the DOM outside of Vue’s patch stage, so you would find a way to somhow move those pieces of code from the worker to the main thread again.

Honestly, I have no clue how performance of the messaging is with bigger data amounts in older browswers (IE), which could also be a problem,

and last but no least, mobile support is not good, and Shared Worker support is even worse.


#9

The data amount wouldn’t be so massive, but having to split plugins would be about as murderous as the browser support.
Thank you for your comment.


#10

The reason is probably that it would require a 80% rewrite.

@LinusBorg, @qm3ster, @andyjessop

Have a look at the comlink lib and the Clooney lib built on top of it.

Thanks to these libs you can run JS code in a Worker thread like it was a normal asynchronous code! It’s awesome.


#11

It’s possible to emulate Shared Worker somewhat by using a Web Worker and a Service Worker.
Every new tab messages the Service Worker, and gets back a channel to the Web Worker.


#12

Seems like an awesome library indeed - But it says in the README:

Caveat: The class cannot rely on its surrounding scope, since it is executed in an isolated context.

Which is kind of the point of my claim that we would need an 80% rewrite: In its current implementation, the virtualDOM contains references to HTMLElements, for example - and HTMLElement doesn’T exist in WebWorkers.

In the patch phase, we add/remove Event listeners, which would not work in the WebWorker as well, obviously.

Other such limitations exist. So we would have to spit the work that can be done in a WebWorker from the work that can’t, which would require some big changed, even with the support of a lib such as Clooney.

That being said, moving vuex to a webworker could be possible with a justifiable amount of work, in theory.


#13

Thanks for the detailed explanation!


#14

I’d like to bring this idea back to life because Google is working on the open source implementation of DOM inside a Web Worker - worker-dom, which will be used in AMP as the <amp-script> component. This opens a whole new world of JS in AMP HTML, which for now is forbidden. I think Vue.js should take this opportunity and get ready to be used in Web Workers for better performance and compliance with ever-growing AMP.

As an example, Aurelia vNext aims to run inside worker-dom.

Which DOM API does Vue.js use internally? How much work would it take to make Vue.js compatible with worker-dom?


#15

Thanks for this information it’s great. :grin:


#16

There is an open issue to make Vue.js run in worker-dom. This should be even easier in Vue 3.0, which will be platform-agnostic.


#17

Update: I made a TodoMVC demo with Vue.js running in worker-dom and it works - see the demo.

Update 2: I went a step further and made a TodoMVC demo with Vue.js in <amp-script>, see the source code. In order for it to run, you have to open Console, type AMP.toggleExperiment('amp-script') and reload the page in order to enable the <amp-script> component.


#18

At the AMP Conf 2019 it was officially announced that <amp-script> supports Vue.js! There was even a working TodoMVC demo: