Web Worker driven Classes and methods
Don’t miss the Web Worker driven NodeJS follow up!
Once upon a time, I used to expose ActionScript features via an interface that was usable through JS. That was enabling file reading, or writing, as well as audio or video playback, plus other System capabilities the good old Flash used to expose 10 years before the Web provided similar APIs.
This story is not a nostalgic rant though, it’s rather about the ability to interface the main thread with a whole new universe behind the scene.
He dropped 95% of a bundle size with a simple trick
If you haven’t read this recent David East’s post already, I suggest you go through it before keep reading this one.
If you know that story already, or you are really that lazy, this is the quick rephrased summary of what David did:
How did I drop 95% of JavaScript bundle? Well… I put it in another bundle.
But how can I remove code without removing features? Web Workers.
And what did David do to expose his new bundle throug a worker?
He defined his own ad-hoc switch statement to handle “this or that” case per each time a generic event message was passed through:
self.addEventListener('message', event => {
switch(event.data.cmd) {
case 'initializeApp': ...
case 'firestore.col.add': ...
case 'firestore.col.onSnapshot': ...
}
});
This might work for your needs too, but I have enough remote API driven code experience to tell you that above approach will eventually not scale:
- the amount of cases grows with the complexity of the app
- 3rd parts scripts might also use
message
events and interfere, break, or change, the result of your ad-hoc communication channel - there is no consistency between cases so that every operation might have less predictable results
… and this is where I’d like to finally propose what I’ve been doing for at least 5 years, during those times where ActionScript and JavaScript where complenting each other, instead of fighting, with the difference that meanwhile the JavaScript language grew up enough to handle way better asynchronous code, naturally available through any Web Worker.
workway: a general purpose Web Worker exporter
What if you could export a generic namespace full of serializable informations and also capable of exporting utilities as callbacks, or classes with methods?
// file /worker.js
importScripts('https://unpkg.com/workway/worker.js');// import anything else needed and initialize firestorm// expose remotely the following namespace
// either as object or Promise that will resolve one
workway({
utils: {
random: (size = 8) => String.fromCharCode(
...crypto.getRandomValues(new Uint8Array(size))
)
},
app: {
User: class User {
constructor() {
this.uid = firebase.auth().currentUser.uid;
}
getName() {
return firebase.database()
.ref('/users/' + this.uid)
.once('value')
.then(snapshot => ((
snapshot.val() && snapshot.val().username
) || 'Anonymous'));
}
}
}
});// generic message listeners (addEventListener works too)
self.onmessage = event => console.log(event.data);
So you have that worker, which offload the main thread, exposing some utlity and some class, and you’d like to use all of that through your main thread, which might look just like this:
// file /bundle.js for the main thread
workway('/worker.js').then(async ({worker, namespace}) => {
const user = new namespace.app.User;
user.getName().then(name => {
worker.postMessage(`Thanks from ${name}!`);
});
const rand = await namespace.utils.random(16);
console.log(rand);
});
The worker will eventually log the user name, and there will be a user reflected on the client side with a class full of methods (statics or prototype) that can be used right away, together with all other utilities and properties: how simple is that?
Offload as much as possible for a cost of 0.5K !
You read that write, the client file of the library, written in a super friendly ES5 syntax, hence tested and compatible down to IE10, weights just .5K via brotli, or .6K using gzip: batteries included!
It’s an easy to maintain and reason about 100LOC of file with a worker related companion, wich size is also irrelevant for any small to huge Web application.
Key features
The benefits of using this tiny module compared to any ad-hoc solution are summarized in here:
- compatible with IE 10, without using Proxy or code evaluation, so actually compatible with iOS 8, Android 4.4, and pretty much every browser
- worker events and messages do not interfere with each other. What’s destined to be handled by workway will, everything else won’t, leaving room to third parts Web Worker scripts to behave as expected.
- The behavior is standardized per each remote invoke: every utility, callback, public static or instance method returns a promise that will resolve the returned value (that could be a promise too).
- instances on the client are automatically reflected on the Worker, including runtime properties, if added, as long as these are serializable
- arguments, serializable as well, are automatically handled too, so you just write regular JS and everything else is handled out of the box
As Summary
It’s becoming more common than ever to move part of complex applications off the main thread that having a lightweight, super compatible, solution that makes writing and consuming JS remotely so easy, might become a game changer for those in search of that super speedy first meaningful paint.
I hope you like this little workway utility, and I’d be more than happy to answer any question around it.