How Does Partytown Work

Traditionally, communicating between the main thread and worker thread must be asynchronous. Meaning that 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.

For example, the code below works as expected within a web worker:

const rect = element.getBoundingClientRect();
console.log(rect.x, rect.y);

First thing you’ll notice is that there’s no async/await, promise or callback. Instead, the call to getBoundingClientRect() is blocking, and the returned rect value contains the expected x and y properties.

Partytown relies on Web Workers, Service Workers, JavaScript Proxies, and a communication layer between them all.

There are currently two ways to communicate synchronously between the web worker and main thread, and that’s sync xhr requests combined with Service Workers, and Atomics.

Designating Web Worker Scripts

It’s important to note that Partytown does not automatically move all scripts to the web worker, but prefers an opt-in approach. Meaning, it’s best that the developer can pick and choose exactly which scripts should use Partytown, while all the others would go unchanged. Please see the Partytown Scripts for more info.

Partytown is only enabled for specific scripts when they have the type="text/partytown" attribute. This type attribute does two things:

  1. Prevent the main thread from executing the script.
  2. Provides a selector for Partytown to query, such as document.querySelectorAll('script[type="text/partytown"]')

Adding Partytown Attribute

Below is an example of adding the type="text/partytown" attribute to an existing <script>.

- <script>...</script>
+ <script type="text/partytown">...</script>

Service Worker

  1. Scripts are disabled from running on the main thread by using the type="text/partytown" attribute on the <script/> tag.
  2. Service worker creates an onfetch handler to intercept specific requests.
  3. Web worker is given the scripts to execute within the worker thread.
  4. Web worker creates JavaScript Proxies to replicate and forward calls to the main thread APIs (such as DOM operations).
  5. Any call to the JS proxy uses synchronous XHR requests.
  6. Service worker intercepts requests, then is able to asynchronously communicate with the main thread.
  7. When the service worker receives the results from the main thread, it responds to the web worker’s request.
  8. From the point of view of code executing on the web worker, everything was synchronous, and each call to the document was blocking.

Atomics

Please see the Atomics communication layer docs on how to enable them. When Atomics are not enabled, the fallback is to use the Service Worker instead. In the end, Atomics are preferred because they’re roughly 10x faster in transfering data between the web worker and main thread.

  1. Scripts are disabled from running on the main thread by using the type="text/partytown" attribute on the <script/> tag.
  2. Main thread detects Atomics communication can be used, and loads the Atomics build instead of the Service Worker build.
  3. Web worker is given the scripts to execute within the worker thread.
  4. Web worker creates JavaScript Proxies to replicate and forward calls to the main thread APIs (such as DOM operations).
  5. Any call to the JS proxy will use Atomics.store() and postMessage() to send the data to the main thread, and run Atomics.wait().
  6. Atomics.load() is used once the web worker recieves the results data from the main thread.
  7. From the point of view of code executing on the web worker, everything was synchronous, and each call to the document was blocking.

Serialization

Data passed between the main thread and web worker must be serializable. Partytown automatically handles the serializing and deserializing of data passed between threads. This works for primitive values, such as a string, boolean or number, and also functions, which get unique IDs assigned that are passed to the opposite thread and can be called.