Use Web Workers in your Vue.js Components for Max Performance
In the first vuedose VueDose tip ever I revealed a trick to improve performance on large lists. Yay, that was a great start!.
However not all comes into that case.
Sometimes you need to use a component that is heavy to create and render, usually because they perform complex tasks. I came up into that case yesterday.
I was creating a page using StoryBlok. They have the amazing feature of creating a rich-text field that content managers can use in order to enter any kind of formatted text such as lists, images, block quotes, bold, italics...
When you get the rich-text content from the StoryBlok API, it has its own structure. In order to render that data into HTML, you must use the richTextResolver.render(content)
method of storyblok-js-client
.
We can encapsulated this functionality into a RichText.vue
component. A basic implementation would be:
<template>
<div v-html="contentHtml"></div>
</template>
<script>
export default {
props: ["content"],
computed: {
contentHtml() {
return this.$storyapi.richTextResolver.render(content);
}
}
};
</script>
Note: $storyapi
it's an instance of the StoryBlok Client that comes from StoryBlok Nuxt.js module, which is what I'm using, but that's not relevant for the purpose of this article
Nothing too fancy so far. Just... here comes the surprise.
It seems like that render it's a heavy task, which was starting to be noticable when rendering several of this components with a decent amount of content.
Now imagine this situation: you have a list of text-rich components in your page, and a dropdown to filter by. When you change your dropdown filter, you re-fetch all content given that filter, and the list re-renders.
Here's where you could notice the heavyness of richTextResolver.render
: the dropdown was lagging to close after selecting it's value.
The reason is that the default JavaScript execution runs in the main thread, which is UI-blocking.
Problem understood. So... how can we fix it?
Easy: using a Web Worker for the rich-text rendering task.
Note: I'm not diving into Web Workers, but check its docs for more info.
Web Workers run in a separate thread and they're not UI-blocking, perfect for our case. We're not diving into web workers, but check its docs for more info.
Keep in mind that web workers run in their own context, and by default we cannot access external contexts. But we need to access the storyblok-js-client
npm module. For that, Webpack comes to the rescue with worker-loader
.
First, install it by running `npm install -D worker-loader. Then you'd need to configure it. In Nuxt.js you'd do it in nuxt.config.js in this way:
build: {
extend(config, { isDev, isClient }) {
config.module.rules.push({
test: /\.worker\.js$/,
use: { loader: "worker-loader" }
});
}
}
With this configuration, all *.worker.js files will be processed by worker-loader
.
Let's create a render-html.worker.js
:
import StoryblokClient from "storyblok-js-client";
let storyClient = new StoryblokClient({});
// When the parent theard requires it, render the HTML
self.addEventListener("message", ({ data }) => {
const result = storyClient.richTextResolver.render(data);
self.postMessage(result);
});
That's a basic implementation of a worker. You need to listen to message
event, which is the way you'll communicate to it from your Vue.js app. Then you can get the data
of the event, render it with storyblok-js-client
and send up the result self.postMessage
.
Let's update the RichText.vue
component to use the service worker:
<template>
<div v-html="contentHtml"></div>
</template>
<script>
import Worker from "./render-html.worker.js";
// Create the worker instance
const worker = new Worker();
export default {
props: ["content"],
data: () => ({
contentHtml: ""
}),
mounted() {
// Update the state with the processed HTML content
worker.onmessage = ({ data }) => {
this.contentHtml = data;
};
// Call the worker to render the content
worker.postMessage(this.content);
}
};
</script>
# The result
Do you want to know how much performance improvement we achieved by this? Sure, Web Performance doesn't make sense if you don't measure it.
In fact, I have an article for you to learn and undestand how to meassure performance in Vue.js components. So make sure to read it to better understand the following test.
I've meassured this component with a medium-size on my mac, 6x throttle.
The results are: 20.65x faster on component's render and 1.39x on patch.
If you don't know what render and patch mean, it's explained in this article
That's it for today's tip!
Related Articles
Achieve Max Performance loading your images with v-lazy-image
Don't you know what to do to improve your web performance? Learn Progressive Image Loading, a technique used by Spotify, Medium and Netflix.
Aug 30, 2021
Use Responsive Images with v-lazy-image
Is Web Performance a priority for you? Then read this article! You'll learn how to lazy load images with srcset and picture tag using v-lazy-image.
Aug 23, 2021
Lazy load images using v-lazy-image Vue.js component
Learn to improve the web performance of your vue.js application
Apr 16, 2019
Sponsors
VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.