Coder Social home page Coder Social logo

web4more / html-workers Goto Github PK

View Code? Open in Web Editor NEW

This project forked from jimmywarting/whatwg-worker

0.0 1.0 0.0 129 KB

๐Ÿงต Launch a new Worker() in Node.js, Deno, or the browser

JavaScript 16.74% TypeScript 83.26%
javascript nodejs polyfill sharedworker threading web-worker worker

html-workers's Introduction

Isomorphic HTML Worker threads

๐Ÿงต Launch a new Worker() in Node.js, Deno, or the browser

๐Ÿ‘ทโ€โ™‚๏ธ Lets you use new Worker() anywhere
๐Ÿค Implements SharedWorker!
0๏ธโƒฃ Zero-cost when used in the browser
โœจ WebIDL-compliant

Installation

You can install this package using npm, Yarn, pnpm, or your other favorite npm package manager. ๐Ÿ“ฆ

npm install @webfill/html-workers

If you're using Deno, you can use the new npm: imports or just import it straight from an npm CDN like esm.sh or jsDelivr.

import "npm:@webfill/html-workers";
import "https://esm.sh/@webfill/html-workers";

For browser users, just import it via a CDN like esm.sh or jsDelivr. You'll get a lot of re-exported globals for zero-cost. ๐Ÿคฉ

import "https://esm.sh/@webfill/html-workers";
import "https://esm.run/@webfill/html-workers";

๐Ÿ›‘ The SharedWorker shim implementation for browsers will only be loaded if no SharedWorker exists. This is not always zero-cost. Take a look at the SharedWorker support matrix on caniuse.com. Support is notably lacking on mobile device browsers. ๐Ÿ˜ญ

Usage

The easiest way to get started is just to import the polyfill in your main thread, and then once in each worker thread. ๐Ÿ›‘ It's on you to import the polyfill on the "inside" of the worker. Why? Because sometimes you might not want to have the polyfill on the inside of the worker.

// main.js
import "@webfill/html-workers";

// ๐Ÿ‘‰
const u = import.meta.resolve("./worker.js");
const w = new Worker(u, { type: "module" });
w.onmessage = (e) => console.log(e.data);
//=> "Hello from file:///worker.js!"
w.postMessage(5000);
// worker.js
import "@webfill/html-workers";

globalThis.postMessage(`Hello from ${location}!`);
globalThis.onmessage = (e) => console.log(e.data);
//=> 5000
const u = `data:text/javascript,console.log("Hello world!")`;
const w = new Worker(u, { type: "module" });
//=> "Hello world!"

โ˜ That code will work in the browser, Node.js, and Deno. ๐Ÿ˜ฑ And guess what? In the browser, zero code will be bundled thanks to compile-time export conditions! ๐Ÿคฉ

โš ๏ธ Node.js can't import blob: URLs

Node.js will fail if you do this:

const b = new Blob([`console.log(42)`], { type: "text/javascript" });
const u = URL.createObjectURL(b);
await import(u);
//=> Uncaught: Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: ...

There are a couple relevant issues tracking this. [nodejs/node#47573] tracks the import("blob:") case, while [nodejs/node#46557] tracks the support for cross-thread access to blob: URLs (including fetch()).

If you want to use blob: URLs with new Worker() before those things happen, you can use the @jcbhmr/bburls package like this:

export NODE_OPTIONS='--experimental-loader=@jcbhmr/bblobu --import=@jcbhmr/bblobu'
node main.js

โ˜ This will add a custom blob: import loader as well as add a fetch() decorator to support blob: URLs across threads. Check out jcbhmr/bblobu for more information.

๐Ÿ”— If you're interested in import("https://...") support, check out other Node.js HTTP loaders like the Node.js docs HTTP loader or node-loader/node-loader-http.

๐Ÿ‘€ Also check out the esmurl package which lets you write your worker thread code right next to your main thread code. Oh, and it works with bundlers! ๐Ÿ˜Š

// main.js
const u = esmurl(import.meta, async () => {
  await import("@webfill/html-workers");
  const { default: isOdd } = await import("is-odd");
  globalThis.onmessage = (e) => {
    const n = e.data;
    const r = isOdd(n);
    globalThis.postMessage(r);
  };
});

const w = new Worker(u, { type: "module" });
w.onmessage = (e) => console.log(e.data);
w.postMessage(5000);
//=> false
w.postMessage(5001);
//=> true

If you're looking to use this package as a ponyfill (like for a library), you can import any of the direct files that we expose as sub-path exports.

// main.js
import Worker from "@webfill/html-workers/Worker.js";

console.log("Worker" in globalThis);
//=> false
const u = import.meta.resolve("./worker.js");
const w = new Worker(u, { type: "module" });
//=> "Hello world!"
w.onmessage = (e) => console.log(e.data);
//=> "Hello from file:///worker.js!"
// worker.js
import self from "@webfill/html-workers/self.js";
import workerThreads from "node:worker_threads";

console.log(workerThreads.workerData);
//=> { constructorName: "Worker", type: "module", ... }
console.log("DedicatedWorkerGlobalScope" in globalThis);
//=> false

self.postMessage(`Hello from ${self.location}!`);

โ„น self from @webfill/html-workers/self.js is an instance of DedicatedWorkerGlobalScope. It doesn't have any globalThis props.

SharedWorker threads

Don't forget! We implement the SharedWorker class too. It works just like you expect it to in the browser. Remember, SharedWorker instances are keyed by the constructor URL, not the final/redirected URL.

// main.js
import "@webfill/html-workers";

var u = import.meta.resolve("./shared.js");
const s = new SharedWorker(u, { type: "module" });
s.port.postMessage(10);

var u = import.meta.resolve("./worker.js");
new Worker(u, { type: "module" });
new Worker(u, { type: "module" });
await new Promise((r) => setTimeout(r, 100));

s.port.onmessage = (e) => console.log(e.data);
//=> 10
//=> 110
//=> 210
// shared.js
import "@webfill/html-workers";

const u = import.meta.resolve("./worker.js");
let n = 0;
globalThis.onconnect = (e) =>
  (e.ports[0].onmessage = (e) => console.log((n += e.data)));
// worker.js
import "@webfill/html-workers";

const u = import.meta.resolve("./shared.js");
const w = new SharedWorker(u, { type: "module" });
w.postMessage(100);

What's included

This package focuses mostly on stuff that is on the workers.html page of the HTML spec (that's where we got the name html-workers from!). That means things like Worker and AbstractWorker are here, but ServiceWorker isn't.

  • Worker class
  • AbstractWorker interface
  • WorkerGlobalScope class
  • DedicatedWorkerGlobalScope class
  • WorkerLocation class
  • WorkerNavigator class
  • SharedWorker class
  • SharedWorkerGlobalScope class
  • WorkerOptions type

It's important to note that the navigator and location globals that are exposed on the "inside" of a new Worker() do not include some of the typical web mixin classes like NavigatorUA. You can add these yourself in your own code if you like:

// worker.js
import "@webfill/ua-client-hints";

console.log(navigator.userAgentData.brands);
//=> [{ brand: "Node.js", version: "v20.0.0" }]

Development

To get started, run npm install and npm run dev to start the dev loop! We use tsx + node:test for testing. You can run npm test to run just the tests.

npm install
npm run dev

To run the build script, run npm run build. This will generate the dist/ folder with the ESM output files. We are using plain tsc.

npm run build

This package uses TypeScript. We want to use TypeScript so that we get easy code co-location for all the complex types we want to declare. We also get access to Stage 3 JavaScript features like using and @decorators! We use @decorators to make the WebIDL implementations a lot more readable vs. applying these WebIDL annotations after the fact.

@interface_("Worker")
@constructor([USVString, optional(WorkerOptions)])
class Worker {
  constructor(scriptURL: USVString, options?: WorkerOptions) {}
  @eventHandlerIDLAttribute("message")
  accessor onmessage: EventHandler;
  @eventHandlerIDLAttribute("messageerror")
  accessor onmessageerror: EventHandler;
}
What the alternative JavaScript version looks like
class Worker {
  constructor(scriptURL, options) {}
}
Worker = interface_("Worker")(Worker);
Worker = constructor([USVString, optional(WorkerOptions)])(Worker);
defineEventHandlerIDLAttribute(Worker, "onmessage", "message");
defineEventHandlerIDLAttribute(Worker, "onmessageerror", "messageerror");

html-workers's People

Contributors

developit avatar jimmywarting avatar jcbhmr avatar guybedford avatar phated avatar danielroe avatar marvinhagemeister avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.