How Does It Work? - BuilderIO/partytown Wiki

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.

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

  1. TODO

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. The easiest would be primitive values, such as a string, boolean or number.

More complicated, but Partytown also handles, is passing functions between the threads. Technically the "functions" are not between the two threads, but instead, references to functions are assigned unique IDs, and the opposite thread is able to call it.