Arek Nawo Hobbyist. Programmer. Dreamer. Freelancer. JavaScript and TypeScript lover. 👍 World-a-better-place maker. 🌐

Using web workers to boost third-party script performance

5 min read 1510

Using Web Workers to boost third-party script performance

Editor’s note: This article was updated on 12 May 2022 to include more up-to-date information about the Partytown library.

Web workers were met with high expectations after their introduction in 2010. However, as time went on, even though developers used web workers for advanced libraries and use cases (like ML training in TensorFlow or language support in Monaco Editor), they became a last-resort optimization technique for everyday projects. This status quo became only more apparent as Web Assembly (Wasm), and its later support for multi-threading, was introduced.

With that said, there’s a new library called Partytown that aims to use web workers to solve a common problem: moving intensive third-party scripts execution off the main thread. Let’s take a closer look.

Contents

What are third-party scripts?

Before we talk about Partytown, we should understand its goal first.

Third-party scripts are a common sight across the vast majority of websites. They’re responsible for injecting analytics (Google Analytics), ads (Google Adsense), processing payments (Stripe), displaying embeds (Disqus), and more. In general, creating a modern web app is almost impossible without including at least a single third-party script in your code.

But while third-party scripts are extremely helpful, they have one enormous disadvantage: they hurt your website’s performance. With each script often being a multi-kB or even MB-sized “black box” of code, it’s no wonder that with just a few of them, your website will scramble.
This is where Partytown comes into play.

Introducing Partytown

Partytown is a new library with the goal of speeding up your website by decluttering the main thread from the burden of third-party scripts. It aims to achieve this with the use of web workers.

On the surface, it seems like a simple idea. Just take a script and put it inside a web worker, right? Not really — issues start to arise when we take into account the limitations of web workers.

Web workers can’t:

  • Access the DOM
  • Fully access window object’s methods and properties
  • Be directly loaded from different origins

As you can imagine, these limitations make most third-party scripts impossible to run from a web worker on its own — that is, without making additional adjustments.

To allow your third-party script to run inside a Web Worker, Partytown uses a few tricks:

  1. It disables the execution of scripts with type="text/partytown" attribute on the main thread, fetches their content, and runs them inside a web worker through Blobs
  2. Inside the web worker, it creates dedicated Proxy objects for accessing the DOM and other globals available in the main thread. These objects handle communication with the main thread and execute actions that can’t be done from within a web worker
  3. To make communication synchronous (as the third-party scripts expect it to be), Partytown uses a combination of synchronous HTTP requests and service workers to intercept and, in a given time, respond to incoming requests
  4. For faster and more efficient communication, Partytown recently added support for Atomics. With this improvement, Partytown can provide much better experience and performance on modern browsers

As you can see, there are many workarounds involved to make Partytown possible. Add to that the sandboxing iframe that wraps the web worker, and you can see how complex of a project Partytown really is. That’s partially why the library is still in beta and might not yet work “out-of-the-box” in some cases.

With that said, there are a couple of integrations already tested by the Partytown development team, and we’ll be taking a look at one of them to see how the library works.

Getting started with Partytown test cases

Partytown provides configurations for tested services, like HubSpot Forms, Intercom, and Google Tag Manager (GTM).

Preparing the environment

Before you get to the code, you first need to set up your environment. Create a new npm project and install the Partytown module:

npm init -y
npm install @builder.io/partytown

Now, that was pretty normal for any modern JavaScript library. However, the next, more unconventional step is to get the lib directory’s content from the @builder.io/partytown npm module available through the /~partytown/ route of your website.

If your setup uses, for example, a public directory for that, you can do it with a simple command provided by Partytown CLI:

partytown copylib ./public/~partytown/

For popular module bundlers like Vite, Rollup, or webpack, Partytown comes with dedicated plugins.

Initializing Partytown

With the module and lib’s content in place, you can now initialize Partytown. To do so, you have to inline the Partytown snippet into your page:

import { partytownSnippet } from '@builder.io/partytown/integration';

const snippetContent = partytownSnippet();
const script = document.createElement("script");

script.textContent=snippetContent;
document.body.append(script)

You can use the above code to initialize Partytown in any JavaScript app. Partytown also provides dedicated integrations for popular UI frameworks.

If you want to configure Partytown to turn on the debugging mode or customize lib’s content location, you should declare a global partytown object with the necessary options:

window.partytown = {
  debug: true,
};

Integrating HubSpot Forms with Partytown

With Partytown correctly set up, we’ll now integrate HubSpot Forms.

First, prepare your HubSpot account, an example form, and its embed code. Now, just drop the HubSpot snippet into your HTML file and add the type="text/partytown" attribute to all the <script> tags

<!-- ... -->
<script
  charset="utf-8"
  type="text/partytown"
  src="//js-eu1.hsforms.net/forms/shell.js"
></script>
<script type="text/partytown">
  hbspt.forms.create({
    region: "eu1",
    portalId: "25225394",
    formId: "cd2ac1fe-aad5-4959-a6ad-5409ba10bc44",
  });
</script>
<!-- ... -->

In the ideal scenario, you’d expect the integration to be already done and everything to work well. Sadly, due to the very experimental, “bleeding edge” nature of Partytown, that’s not the case.



Even if the form appears just fine, open the dev console, and you’ll be greeted with a red wall of errors. Some could be CORS, others rendering-related.

Working around CORS and other issues

CORS errors are thrown when Partytown attempts to fetch a cross-origin resource. As the request is now made through a web worker that on its own comes from an iframe, it’s no wonder CORS issues can arise. That’s why Partytown utilizes a proxy service to fall back to after the request fails. But still, the error message remains.

To make the CORS issues disappear, you can pre-download the file and serve it from your origin. You should do the same for all sub-requests of the pre-downloaded scripts as well. However, keep in mind that not all resources can be pre-downloaded, and sometimes such workarounds can break the third-party scripts because of Content Security Policy (CSP) restrictions.

Other, less notable errors don’t affect the functioning of the embed. With CORS fixes implemented, the embed should render and send submissions just fine.

Results

With this integration, we can now test if Partytown works.

To the naked eye, it might actually feel slower. That’s because Partytown’s DOM operations are purposefully throttled. This might not always be desired, but it helps with limiting third-party scripts from continuously blocking the main thread.

When we take a look at the Performance tab in Chrome DevTools, we’ll see Partytown shine.

HubSpot Forms - Partytown (left) vs. standard (right)

 

While differences were negligible at first, with six times the CPU slowdown, Partytown’s advantage becomes truly visible. With over four times less time spent on scripting and 0ms vs. 297ms blocking time over the standard method, Partytown has successfully moved the impact of the third-party script away from the main thread.

Drawbacks

So, as you can see from our walkthrough, even though it’s working, Partytown isn’t production-ready right now. While HubSpot Forms might work with only some issues, there are many other untested third-party scripts that won’t work at all.

Other notable drawbacks include:

  • event.preventDefault() not working
  • Bloated Network DevTools tab due to all the requests used for synchronous cross-thread communication (not applicable if the Atomics are supported)

Bottom line

Partytown works. The idea behind it is excellent, and it achieves its goal. However, given that it’s a very complex library that uses many workarounds to get third-party scripts working inside web workers as intended, its beta status really means beta. Partytown, while on the right track, isn’t production-ready right now.

Overall, it’s a bit concerning that such a library is necessary. As we increasingly depend on third-party scripts, and their performance impact only grows, the speed and efficiency of many modern websites starts to be concerning. Naturally, the best — but also most demanding — solution would be for all script vendors to better optimize their code.

Without that, Partytown and others like it seem like the only option to vastly improve the performance of many websites. And it would do so without requiring each third-party script to be optimized individually. Fingers crossed 🤞

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.

https://logrocket.com/signup/

LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Arek Nawo Hobbyist. Programmer. Dreamer. Freelancer. JavaScript and TypeScript lover. 👍 World-a-better-place maker. 🌐

2 Replies to “Using web workers to boost third-party script performance”

  1. When is this going to be ready? If i give you money will it be ready sooner? Or my first born child?

    I have a kidney and a spleen you can take if it will speed things up please make this third party script nightmare go away

Leave a Reply