1. Welcome
In this lab, you'll take an existing web application add add web worker to share state between two open windows. This is the eighth in a series of companion codelabs for the Progressive Web App workshop. The previous codelab was Service Worker Includes. This is the final codelab in the series.
What you'll learn
- Add a shared worker between multiple open windows
- Use Comlink to make working with workers easier
What you should know
- JavaScript
What you will need
- A browser that supports shared web workers
2. Get Set Up
Start by either cloning or downloading the starter code needed to complete this codelab:
If you clone the repo, make sure you're on the pwa06--working-with-workers
branch. The zip file contains the code for that branch, too.
This codebase requires Node.js 14 or higher. Once you have the code available, run npm ci
from the command line in the code's folder in order to install all of the dependencies you'll need. Then, run npm start
to start the development server for the codelab.
The source code's README.md
file provides an explanation for all distributed files. In addition, the following are the key existing files you'll be working with throughout this codelab:
Key Files
js/preview.js
- Preview page JavaScript filejs/main.js
- Main application JavaScript file
3. Write a Worker
Currently, your web app's preview functionality only shows the latest content on load. Ideally, it'd show a live preview as the user's typing. This requires compiling potentially large amounts of data and ferrying it between two different open windows. Because of this, it's not something we want to be doing on the main thread of any of the open windows. Instead, let's use a shared web worker.
To start, create a file js/worker.js
with the following code:
import { expose } from 'comlink';
import { marked } from 'marked';
class Compiler {
state = {
raw: '',
compiled: '',
};
subscribers = [];
async set(content) {
this.state = {
raw: content,
compiled: marked(content),
};
await Promise.all(this.subscribers.map((s) => s(this.state)));
}
subscribe(cb) {
this.subscribers.push(cb);
}
}
const compiler = new Compiler();
onconnect = (e) => expose(compiler, e.ports[0]);
Explanation
This code sets up a class, called Compiler
, that allows content to be set and allows subscriptions to be called once that content has been compiled. Because it's a shared worker, there should only be a single instance of this class used, so a new instance of Compiler
is instantiated. Then, to make working with this class feel seamless from outside the worker, Comlink is used to expose the compiler instance, allowing us to use all of the methods on it as if it were declared in the code using it. Because this is a shared worker instead of a dedicated worker, it needs to be exposed to all connections.
4. Send Content to the Worker
With the worker created, we now need to send content into it. To do so, update js/main.js
to do the following:
- Import the named export
wrap
fromcomlink
- Create a new module-typed Shared Worker called
worker
, set its type tomodule
, and point to it using thenew URL
pattern (new URL('./worker.js', import.meta.url)
) - Create a
compiler
variable thatwrap
s theworker.port
- In the editor's update function (
editor.onUpdate
), after saving content to the database, wait forcompiler.set
to finish, passing in the content
Explanation
Wrapping a Comlink export allows things like exposed class methods to be used as if they weren't shared across a worker boundary, with an exception being that now everything is asynchronous. Because this is a shared worker instead of a dedicated worker, Comlink needs to wrap the worker's port instead of the worker itself. Now, whenever an update to the editor is made, the content will be sent into the worker to be worked on!
5. Update the Preview Page
The final step is to get the compiled content out of the shared worker into the preview! The setup to do so is largely the same, but because functions can't pass between worker boundary, a proxy for the function needs to be used instead. Comlink, again, is here to help. Update js/preview.js
to do the following:
- Import the named exports
wrap
andproxy
fromcomlink
- Create and wrap the shared worker as you did in
js/main.js
- Call the compiler's
subscribe
method with a proxy function that sets the incoming data'scompiled
property to the inner HTML of the preview area
Once done, open up the preview, start typing in the editor, and be amused and excited watching your markdown automagically compile and appear in real-time in the preview area, all without blocking either page's main thread!
6. Congratulations!
You've learned how to use a shared worker to share state between multiple PWA instances.