Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions docs/examples/06-node-example/main.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { Worker } from "worker_threads";
import * as Comlink from "../../../dist/esm/comlink.mjs";
import nodeEndpoint from "../../../dist/esm/node-adapter.mjs";
import { Worker as NodeWorker } from "node:worker_threads";
import { wrap } from "../../../dist/esm/comlink.mjs";

async function init() {
const worker = new Worker("./worker.mjs");
const worker = new NodeWorker(new URL(import.meta.resolve("./worker.mjs")));

const api = Comlink.wrap(nodeEndpoint(worker));
console.log(await api.doMath());
}
init();
const api = wrap(worker);
console.log(await api.add(6, 7));
11 changes: 5 additions & 6 deletions docs/examples/06-node-example/worker.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { parentPort } from "worker_threads";
import * as Comlink from "../../../dist/esm/comlink.mjs";
import nodeEndpoint from "../../../dist/esm/node-adapter.mjs";
// import { expose } from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
import { expose } from "../../../dist/esm/comlink.mjs";

const api = {
doMath() {
return 4;
add(a, b) {
return a + b;
},
};
Comlink.expose(api, nodeEndpoint(parentPort));
expose(api);
2 changes: 2 additions & 0 deletions docs/examples/08-portable-worker/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!DOCTYPE html>
<script src="./main.mjs" type="module"></script>
14 changes: 14 additions & 0 deletions docs/examples/08-portable-worker/main.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// import { wrap } from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
import { wrap } from "../../../dist/esm/comlink.mjs";
import { PortableWorker } from "./portable-worker.mjs";

const worker = new PortableWorker(import.meta.resolve("./worker.mjs"));
const api = wrap(worker);

// Single call
console.log(await api.add(6, 7));

// Interleaved calls
console.log(
await Promise.all([api.add(4, 5), api.add(6, 7), api.add(100, -5)])
);
15 changes: 15 additions & 0 deletions docs/examples/08-portable-worker/portable-worker.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Worker as NodeWorker } from "node:worker_threads";

type WorkerConstructorOptions = (ConstructorParameters<
typeof globalThis.Worker
> &
ConstructorParameters<typeof NodeWorker>)[1];

interface PortableWorkerConstructor {
new (
url: string | URL,
workerOptions?: Omit<WorkerConstructorOptions, "type">
): Worker | NodeWorker;
}

export const PortableWorker: PortableWorkerConstructor;
89 changes: 89 additions & 0 deletions docs/examples/08-portable-worker/portable-worker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// import { wrap } from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
import { wrap } from "../../../dist/esm/comlink.mjs";

function isCrossOrigin(url) {
if (!globalThis.location) {
return false;
}
const scriptOrigin = globalThis.location.origin;
const workerOrigin = new URL(url, globalThis.location.href).origin;
return scriptOrigin !== workerOrigin;
}

function constructPortableWebWorker(url, workerOptions) {
const useTrampoline = isCrossOrigin(url);

// We could use the trampoline unconditionally, but this would require adding
// `blob:` to the CSP unnecessarily. So we avoid it when possible.
if (useTrampoline) {
// Needed until something like
// https://github.com/lgarron/worker-execution-origin or
// https://github.com/whatwg/html/issues/6911 is available in all browser.
const trampolineSource = `import ${JSON.stringify(url.toString())};`;
const blob = new Blob([trampolineSource], {
type: "text/javascript",
});

url = URL.createObjectURL(blob);
}

const worker = new globalThis.Worker(url, {
...workerOptions,
type: "module",
});

if (useTrampoline) {
const originalTerminate = worker.terminate.bind(worker);
Object.defineProperty(worker, "terminate", {
get() {
URL.revokeObjectURL(url);
originalTerminate();
},
});
}

return worker;
}

function constructNodeStyleWorker(url, workerOptions) {
// We could theoretically use dynamic import, but:
//
// - 1. There is no synchronous way to do this conditionally. We can't do it
// synchronously in a constructor, and while top-level `await` is
// well-supported in runtimes it's not as easy to use with bundlers.
// 2. `.getBuiltinModule(…)` signals more clearly that these are strictly
// runtime dependencies.
const { Worker: NodeWorker } = globalThis.process.getBuiltinModule(
"node:worker_threads"
);

// `import.meta.resolve(…)` is the recommended way to get the path to a
// relative file to pass to the worker constructor. This returns a `file://…`
// URL as a string, which `bun` and `deno` accept for the worker constructor
// but `node` does not. We can detect this and convert it to a `URL` to allow
// more concise, idiomatic code across all platforms.
url =
typeof url === "string" && url.startsWith("file://") ? new URL(url) : url;

return new NodeWorker(url, workerOptions);
}

export function PortableWorker(url, workerOptions) {
const hasWebWorkers = globalThis.Worker;
const hasBuiltinModules = globalThis.process?.getBuiltinModule;

if (hasWebWorkers && !hasBuiltinModules) {
// Browsers
return constructPortableWebWorker(url, workerOptions);
}

const webWorkersHaveUnref = globalThis.Worker?.prototype.unref;

if (hasWebWorkers && hasBuiltinModules && webWorkersHaveUnref) {
// `bun`
return constructPortableWebWorker(url, workerOptions);
} else {
// `node` and `deno`
return constructNodeStyleWorker(url, workerOptions);
}
}
10 changes: 10 additions & 0 deletions docs/examples/08-portable-worker/worker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// import { expose } from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
import { expose } from "../../../dist/esm/comlink.mjs";

export const api = {
add(a, b) {
return a + b;
},
};

expose(api);
37 changes: 32 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
"name": "comlink",
"version": "4.4.2",
"description": "Comlink makes WebWorkers enjoyable",
"main": "dist/umd/comlink.js",
"module": "dist/esm/comlink.mjs",
"types": "dist/umd/comlink.d.ts",
"main": "./dist/umd/comlink.js",
"module": "./dist/esm/comlink.mjs",
"types": "./dist/umd/comlink.d.ts",
"sideEffects": false,
"scripts": {
"build": "rollup -c",
"test:unit": "karma start",
"test:node": "mocha ./tests/node/main.mjs",
"test:portability": "npm run test:portability:node && npm run test:portability:bun && npm run test:portability:deno",
"test:portability:node": "./tests/portability/test.bash node --",
"test:portability:bun": "./tests/portability/test.bash npx -- bun run --",
"test:portability:deno": "./tests/portability/test.bash npx -- deno run --allow-read --",
"test:types": "tsc -p ./tests/tsconfig.json",
"test:types:watch": "npm run test:types -- --watch",
"test": "npm run fmt_test && npm run build && npm run test:types && npm run test:unit && npm run test:node",
"test": "npm run fmt_test && npm run build && npm run test:types && npm run test:unit && npm run test:node && npm run test:portability",
"fmt": "prettier --write './*.{mjs,js,ts,md,json,html}' './{src,docs,tests}/{,**/}*.{mjs,js,ts,md,json,html}'",
"fmt_test": "test $(prettier -l './*.{mjs,js,ts,md,json,html}' './{src,docs,tests}/{**/,}*.{mjs,js,ts,md,json,html}' | wc -l) -eq 0",
"watchtest": "CHROME_ONLY=1 karma start --no-single-run"
Expand All @@ -29,8 +33,10 @@
"devDependencies": {
"@rollup/plugin-terser": "0.4.0",
"@rollup/plugin-typescript": "11.0.0",
"bun": "1.3.6",
"chai": "^4.3.7",
"conditional-type-checks": "1.0.6",
"deno": "2.6.5",
"husky": "8.0.3",
"karma": "6.4.1",
"karma-chai": "0.1.0",
Expand All @@ -45,6 +51,27 @@
"rimraf": "4.1.2",
"rollup": "3.10.1",
"tslib": "2.4.1",
"typescript": "4.9.4"
"typescript": "5.9.3"
},
"files": [
"./dist/",
"./docs/",
"./src/"
],
"exports": {
".": {
"types": "./dist/umd/comlink.d.ts",
"import": "./dist/esm/comlink.mjs",
"require": "./dist/umd/comlink.js"
},
"./node-adapter": {
"types": "./dist/umd/node-adapter.d.ts",
"import": "./dist/esm/node-adapter.mjs",
"require": "./dist/umd/node-adapter.js"
},
"./examples/portable-worker": {
"types": "./docs/examples/08-portable-worker/portable-worker.d.ts",
"import": "./docs/examples/08-portable-worker/portable-worker.mjs"
}
}
}
Loading