According to Google, if a page’s load time goes up from 1 second to 3 seconds, the probability of the user bouncing increases by 32%. In 2018, Google released a statement that speed is now used for landing page factors on Google Search and Ads. Way back in 2012, Fast Company noted that Amazon calculated that a page load slowdown of just one second could cost it $1.6 billion in sales each year.
We set out to solve this situation, so that apps of all sizes will be able to continue to use third-party scripts without the performance hit.
Negative Impacts from Third-Party Scripts
According to Google, these are some of the potential issues after adding third-party scripts:
- Firing too many network requests to multiple servers. The more requests a site has to make, the longer it can take to load.
- CPU-intensive script parsing and execution can delay user interaction and cause battery drain.
- Third-party scripts that were loaded without care can be a single-point of failure (SPOF).
- Insufficient HTTP caching, forcing resources to be fetched from the network often.
- The use of legacy APIs (e.g document.write()), which are known to be harmful to the user experience.
- Excessive DOM elements or expensive CSS selectors.
- Including multiple third-party embeds that can lead to multiple frameworks and libraries being pulled in several times, which exacerbates the performance issues.
- Third-party scripts also often use embed techniques that can block window.onload, even if the embed is using async or defer.
However, it’s near impossible to run a successful business online without using third-party libraries like Google Analytics. Other examples include: metrics, ads, A/B testing, trackers, etc.
What is Partytown?
To counteract the negative effects of third-party scripts listed above, Partytown aims to do the following:
- Free up main thread resources to be used only for the primary web app execution.
- Sandbox third-party scripts and allow or deny their access to main thread APIs.
- Isolate long-running tasks within the web worker thread.
- Reduce layout thrashing coming from third-party scripts by batching DOM setters/getter into group updates.
- Throttle third-party scripts’ access to the main thread.
- Allow third-party scripts to run exactly how they’re coded and without any alterations.
- Read and write main thread DOM operations synchronously from within a web worker, allowing scripts running from the web worker to execute as expected.
How Does Partytown Work?
To put it simply, Partytown adds a worker to allow execution in both the main thread and worker thread.
To understand Partytown, it is important to first understand a few technologies used by modern web browsers:
- Web Workers API: This makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. Partytown’s philosophy is that the main thread should be dedicated to your first-party code, and any scripts that are not required to be in the critical path should be moved to a web worker. Main thread performance is, without question, more important than web worker thread performance.
- XMLHttpRequest (XHR): Objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a web page to update just part of a page without disrupting what the user is doing.
- Service Worker API: Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available). They are intended, among other things, to enable the creation of effective offline experiences, intercept network requests, and take appropriate action based on whether the network is available, and update assets residing on the server. They will also allow access to push notifications and background sync APIs.
Traditionally, communicating between the main thread and worker thread must be asynchronous: for the two threads to communicate, they cannot use blocking calls. Partytown is different. It allows code executed from the web worker to access DOM synchronously. The benefit from this is that third-party scripts can continue to work exactly how they’re coded.
As you can see in the below image, code that is running in a web worker that proxies global variables uses synchronous XHR to make an asynchronous operation synchronous. This gets intercepted by a service worker and the main thread value is retrieved via a postMessage and sent back.
How to Integrate Partytown
You can easily add it to any site with a small snippet, and then tag any scripts that you want to load in the web worker with type="text/partytown".
It’s important to note that Partytown does not automatically move all scripts to the web worker, but takes an opt-in approach. It’s best that the developer can pick and choose exactly which scripts should use Partytown, while all the others would go unchanged. There are several ways to integrate Partytown into your code, which you can find in our Getting Started and Copy Library Files documentation.
Partytown can work with any HTML page and doesn’t require a specific framework. However, to make it easier to use in various frameworks or services, plugins/wrappers can be made for their ecosystem.
Partytown is only enabled for specific scripts when they have the type="text/partytown" attribute.
Here’s a sequence of events:
- Scripts are disabled from running on the main thread by using the `type="text/partytown"` attribute on the `<script/>` tag.
- Service worker creates an `onfetch` handler to intercept specific requests.
- Web workers are given the scripts to execute within the worker thread.
- Service worker intercepts requests, then is able to asynchronously communicate with the main thread.
- When the service worker receives the results from the main thread, it responds to the web worker’s request.
- From the point of view of code executing on the web worker, everything was synchronous, and each call to the document was blocking.
Example Execution Steps
Any scripts that you add type="text/partytown" will load by default in a web worker, but have full access to global variables. The `type="text/partytown"` attribute does two things:
- Informs the browser to not process the script. By giving the script a type attribute which the browser does not recognize: “The embedded content is treated as a data block which won’t be processed by the browser.”
- Provides a query selector so Partytown can find all the scripts to run from within the web worker. When the document is ready and Partytown has initialized, Partytown will then query the selector for all of the script[type="text/partytown"] script elements. You’ll notice that after a script has been executed in the web worker, it’ll then get an updated type attribute of type="text/partytown-x".
And here’s the best part: We use a *synchronous* XHR request to block the worker thread and retrieve the needed value from the main thread.
And now we can use a service worker to intercept the /proxytown request, async postMessage to the main thread to get the needed value, and return it.
And that's it! The end result: You can now seamlessly drop in a whole range of third-party scripts to run in a web worker, removing the main thread performance cost.