7

@okikio/sharedworker, SharedWorkers on all browsers

 2 years ago
source link: https://dev.to/okikio/okikiosharedworker-sharedworkers-on-all-browsers-1ia0
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Cover image for @okikio/sharedworker, SharedWorkers on all browsers
Okiki

Posted on Oct 15

• Originally published at blog.okikio.dev on Oct 15

@okikio/sharedworker, SharedWorkers on all browsers

For bundle.js.org, and astro.build/play, I found that I needed a way to use SharedWorkers reliably on all browsers, so, I decided to make a miniature script that would act as a wrapper around the SharedWorker class, by default it would try to create a SharedWorker but otherwise would switch to normal web workers this made SharedWorkers a type of progressive enhancement.

// WebWorker/SharedWorker compatability class from https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts export class WebWorker implements EventTarget, AbstractWorker { static SharedWorkerSupported = "SharedWorker" in globalThis; ActualWorker: SharedWorker | Worker; constructor(url: string | URL, opts?: WorkerOptions) { if (WebWorker.SharedWorkerSupported) { this.ActualWorker = new SharedWorker(url, opts); } else { this.ActualWorker = new Worker(url, opts); } }

get onmessage() { if (WebWorker.SharedWorkerSupported) { return (this.ActualWorker as SharedWorker)?.port.onmessage; } else { return (this.ActualWorker as Worker).onmessage; } }

set onmessage(value: MessagePort["onmessage"] | Worker["onmessage"]) { if (WebWorker.SharedWorkerSupported) { (this.ActualWorker as SharedWorker).port.onmessage = value as MessagePort["onmessage"]; } else { (this.ActualWorker as Worker).onmessage = value as Worker["onmessage"]; } }

get onmessageerror() { if (WebWorker.SharedWorkerSupported) { return (this.ActualWorker as SharedWorker)?.port.onmessageerror; } else { return (this.ActualWorker as Worker).onmessageerror; } }

set onmessageerror(value: MessagePort["onmessageerror"] | Worker["onmessageerror"]) { if (WebWorker.SharedWorkerSupported) { (this.ActualWorker as SharedWorker).port.onmessageerror = value as MessagePort["onmessageerror"]; } else { (this.ActualWorker as Worker).onmessageerror = value as Worker["onmessageerror"]; } }

start() { if (WebWorker.SharedWorkerSupported) { return (this.ActualWorker as SharedWorker)?.port.start(); } }

/** * Clones message and transmits it to worker's global environment. transfer can be passed as a list of objects that are to be transferred rather than cloned. */ postMessage(message: any, transfer?: Transferable[]) { if (WebWorker.SharedWorkerSupported) { return (this.ActualWorker as SharedWorker)?.port.postMessage(message, transfer); } else { return (this.ActualWorker as Worker).postMessage(message, transfer); } }

/** * Aborts worker's associated global environment. */ terminate() { if (WebWorker.SharedWorkerSupported) { return (this.ActualWorker as SharedWorker)?.port.close(); } else { return (this.ActualWorker as Worker).terminate(); } }

close() { return this.terminate(); }

get port() { return (this.ActualWorker as SharedWorker).port; }

get onerror() { return this.ActualWorker.onerror; }

set onerror(value: (this: AbstractWorker, ev: ErrorEvent) => any) { this.ActualWorker.onerror = value; }

addEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker | SharedWorker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { if (WebWorker.SharedWorkerSupported && type !== "error") { return (this.ActualWorker as SharedWorker)?.port.addEventListener(type, listener, options) } else { return this.ActualWorker.addEventListener(type, listener, options); } }

removeEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker | SharedWorker, ev: WorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) { if (WebWorker.SharedWorkerSupported && type !== "error") { return (this.ActualWorker as SharedWorker)?.port.removeEventListener(type, listener, options) } else { return this.ActualWorker.removeEventListener(type, listener, options); } }

dispatchEvent(event: Event) { return this.ActualWorker.dispatchEvent(event); } }

export default WebWorker;

When I realized that a polyfill/ponyfill doesn't exist for SharedWorkers I realized I needed to make one, and to ensure reliable that the polyfill was thoroughly vetted and tested for cross browser compatibility, so, I made @okikio/sharedworker.

GitHub logo okikio / sharedworker

A small spec. compliant polyfill for SharedWorkers, it acts as a drop in replacement for normal Workers.

@okikio /sharedworker

NPM | Github | Docs | Licence

A small mostly spec. compliant polyfill/ponyfill for SharedWorkers, it acts as a drop in replacement for normal Workers, and supports a similar API surface that matches normal Workers.

Ponyfills are seperate modules that are included to replicate the functionality of the original API, but are not required to be used, while polyfills are update the original API on the global scope if it isn't supported in that specific environment or it's feature set is lacking compared to modern variations.

Installation

npm install sharedworker
Enter fullscreen modeExit fullscreen mode

Others

Usage

import { SharedWorkerPolyfill as SharedWorker } from "@okikio
/sharedworker";
// or 
import SharedWorker from "@okikio
/sharedworker";
Enter fullscreen modeExit fullscreen mode

You can also use it directly through a script tag:

<script src="https://unpkg.com/@okikio
/sharedworker" type="module"></script>
<script type="module">
Enter fullscreen modeExit fullscreen mode

Usage

@okikio/sharedworker is a small mostly spec. compliant polyfill/ponyfill for SharedWorkers, it acts as a drop in replacement for normal Workers, and supports a similar API surface that matches normal Workers.

You use it like this,

shared-worker.js

/* 
 * All variables and values outside the `start(...)` function are shared between all pages, this behavior can cause unexpected bugs if you're not careful
 */
const start = (port) => {
    // All your normal Worker and SharedWorker stuff should just work
    // With no more setup 

    /** 
     * All variables and values inside the `start(...)` function are isolated to each page, and will be allocated seperately per page. 
     */
    port.onmessage = ({ data }) => {
        if (data == "Hey")
            port.postMessage("Hello, from the SharedWorker."); 
    };
};

self.onconnect = e => {
    let [port] = e.ports;
    start(port);
};

// This is the fallback, just in case the browser doesn't support SharedWorkers
if ("SharedWorkerGlobalScope" in self) 
    start(self);

Enter fullscreen modeExit fullscreen mode

main.js

import SharedWorker from "@okikio/sharedworker";

const sharedworker = new SharedWorker(new URL("shared-worker.js", import.meta.url));
sharedworker.onmessage = ({ data }) => {
    console.log(data); //= Hello, from SharedWorker
};

sharedworker.postMessage("Hey");

Enter fullscreen modeExit fullscreen mode

In the cases of bundle.js.org and astro.build/play, @okikio/sharedworker was used for esbuild as well as the monaco-editors editor and typescript workers.

Limitation

The major limitation with @okikio/sharedworker is that on browsers that don't support SharedWorker, you can't use @okikio/sharedworker as a cross tab, communication tool. But for everything else it's feature parity and spec. compliance should be great.

Conclusion

So, will you use it? Tell me below, or say Hi on twitter.

Okiki Ojo profile image
Okiki Ojo
@okikio_dev
twitter logo
18:43 PM - 15 Oct 2021

Image from Tengyart on Unsplash.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK