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!

Remember you can read this tip online (with copy/pasteable code), and don’t forget to share VueDose with your colleagues, so they also know about these tips as well!

See you soon.

Start saving time and get a tip about the Vue ecosystem every week, right in your inbox.

Latest Vue Jobs