@b9g/node-webworker 0.1.2 → 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -11
- package/package.json +2 -11
- package/src/index.d.ts +32 -22
- package/src/index.js +147 -44
- package/src/worker-wrapper.js +0 -15
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Minimal Web Worker shim for Node.js until native support arrives.
|
|
|
6
6
|
|
|
7
7
|
Node.js lacks native Web Worker support, despite being a web standard since 2009. This package provides a minimal, reliable shim using Node.js `worker_threads` until native support is added.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Canonical Issue:** https://github.com/nodejs/node/issues/43583
|
|
10
10
|
**Please 👍 and comment** on the issue to show demand for native Web Worker support!
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
@@ -18,7 +18,7 @@ npm install @b9g/node-webworker
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
|
-
import {
|
|
21
|
+
import {Worker} from '@b9g/node-webworker';
|
|
22
22
|
|
|
23
23
|
// Create a worker (same API as Web Workers)
|
|
24
24
|
const worker = new Worker('./worker.js', { type: 'module' });
|
|
@@ -32,22 +32,34 @@ worker.addEventListener('message', (event) => {
|
|
|
32
32
|
worker.postMessage({ hello: 'world' });
|
|
33
33
|
|
|
34
34
|
// Terminate when done
|
|
35
|
-
|
|
35
|
+
worker.terminate();
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Exports
|
|
39
|
+
|
|
40
|
+
### Classes
|
|
41
|
+
|
|
42
|
+
- `Worker` - Web Worker implementation using Node.js worker_threads
|
|
43
|
+
- `MessageEvent` - Event class for worker messages
|
|
44
|
+
- `ErrorEvent` - Event class for worker errors
|
|
45
|
+
|
|
46
|
+
### Default Export
|
|
47
|
+
|
|
48
|
+
- `Worker` - The Worker class
|
|
49
|
+
|
|
38
50
|
## Features
|
|
39
51
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
52
|
+
- **Standards-compliant API** - Drop-in replacement for Web Workers
|
|
53
|
+
- **ES Module support** - Works with modern JavaScript
|
|
54
|
+
- **Minimal overhead** - Thin wrapper around `worker_threads`
|
|
55
|
+
- **Error handling** - Proper event forwarding
|
|
56
|
+
- **Clean termination** - Resource cleanup
|
|
45
57
|
|
|
46
58
|
## Limitations
|
|
47
59
|
|
|
48
|
-
- **Transferable objects** - Limited support (logs warning)
|
|
49
60
|
- **Node.js only** - Don't use this in browsers (they have native Web Workers)
|
|
50
|
-
- **
|
|
61
|
+
- **Module workers only** - Classic workers with `importScripts()` not supported
|
|
62
|
+
- **Serialization differences** - Uses Node.js structured clone, not web's algorithm
|
|
51
63
|
|
|
52
64
|
## Deprecation Notice
|
|
53
65
|
|
|
@@ -57,4 +69,4 @@ We maintain this as a temporary workaround. Please help push for native support
|
|
|
57
69
|
|
|
58
70
|
## License
|
|
59
71
|
|
|
60
|
-
MIT
|
|
72
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/node-webworker",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
4
|
"description": "Minimal Web Worker shim for Node.js until native support arrives",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"worker",
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@b9g/libuild": "^0.1.
|
|
15
|
-
"bun-types": "latest",
|
|
14
|
+
"@b9g/libuild": "^0.1.18",
|
|
16
15
|
"@types/node": "^18.0.0"
|
|
17
16
|
},
|
|
18
17
|
"engines": {
|
|
@@ -34,14 +33,6 @@
|
|
|
34
33
|
"./index.js": {
|
|
35
34
|
"types": "./src/index.d.ts",
|
|
36
35
|
"import": "./src/index.js"
|
|
37
|
-
},
|
|
38
|
-
"./worker-wrapper": {
|
|
39
|
-
"types": "./src/worker-wrapper.d.ts",
|
|
40
|
-
"import": "./src/worker-wrapper.js"
|
|
41
|
-
},
|
|
42
|
-
"./worker-wrapper.js": {
|
|
43
|
-
"types": "./src/worker-wrapper.d.ts",
|
|
44
|
-
"import": "./src/worker-wrapper.js"
|
|
45
36
|
}
|
|
46
37
|
}
|
|
47
38
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -6,20 +6,27 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://github.com/nodejs/node/issues/43583
|
|
8
8
|
*/
|
|
9
|
-
import { Worker as NodeWorker } from 'worker_threads';
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* Message event for worker communication
|
|
12
11
|
*/
|
|
13
|
-
export
|
|
12
|
+
export declare class MessageEvent extends Event {
|
|
14
13
|
readonly data: any;
|
|
15
|
-
|
|
14
|
+
constructor(data: any);
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Error event
|
|
17
|
+
* Error event for worker errors
|
|
19
18
|
*/
|
|
20
|
-
export
|
|
19
|
+
export declare class ErrorEvent extends Event {
|
|
21
20
|
readonly error: Error;
|
|
22
|
-
|
|
21
|
+
constructor(error: Error);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Close event for worker termination
|
|
25
|
+
* Includes exit code for crash detection
|
|
26
|
+
*/
|
|
27
|
+
export declare class CloseEvent extends Event {
|
|
28
|
+
readonly code: number;
|
|
29
|
+
constructor(code: number);
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
32
|
* Web Worker API implementation using Node.js worker_threads
|
|
@@ -28,11 +35,14 @@ export interface ErrorEvent {
|
|
|
28
35
|
* to Node.js worker_threads underneath.
|
|
29
36
|
*/
|
|
30
37
|
export declare class Worker {
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
#private;
|
|
39
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
40
|
+
onerror: ((event: ErrorEvent) => void) | null;
|
|
41
|
+
onmessageerror: ((event: MessageEvent) => void) | null;
|
|
42
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
43
|
+
constructor(scriptURL: string | URL, _options?: {
|
|
44
|
+
type?: "classic" | "module";
|
|
45
|
+
env?: Record<string, string>;
|
|
36
46
|
});
|
|
37
47
|
/**
|
|
38
48
|
* Send a message to the worker
|
|
@@ -41,20 +51,20 @@ export declare class Worker {
|
|
|
41
51
|
/**
|
|
42
52
|
* Add an event listener (Web Worker API)
|
|
43
53
|
*/
|
|
44
|
-
addEventListener(type:
|
|
45
|
-
addEventListener(type:
|
|
54
|
+
addEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
55
|
+
addEventListener(type: "error", listener: (event: ErrorEvent) => void): void;
|
|
56
|
+
addEventListener(type: "messageerror", listener: (event: MessageEvent) => void): void;
|
|
57
|
+
addEventListener(type: "close", listener: (event: CloseEvent) => void): void;
|
|
46
58
|
/**
|
|
47
59
|
* Remove an event listener
|
|
48
60
|
*/
|
|
49
|
-
removeEventListener(type:
|
|
50
|
-
removeEventListener(type:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
terminate(): Promise<number>;
|
|
61
|
+
removeEventListener(type: "message", listener: (event: MessageEvent) => void): void;
|
|
62
|
+
removeEventListener(type: "error", listener: (event: ErrorEvent) => void): void;
|
|
63
|
+
removeEventListener(type: "messageerror", listener: (event: MessageEvent) => void): void;
|
|
64
|
+
removeEventListener(type: "close", listener: (event: CloseEvent) => void): void;
|
|
55
65
|
/**
|
|
56
|
-
*
|
|
66
|
+
* Terminate the worker (Web Worker standard - returns void, not a promise)
|
|
57
67
|
*/
|
|
58
|
-
|
|
68
|
+
terminate(): void;
|
|
59
69
|
}
|
|
60
70
|
export default Worker;
|
package/src/index.js
CHANGED
|
@@ -1,87 +1,190 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
2
|
// src/index.ts
|
|
3
3
|
import { Worker as NodeWorker } from "worker_threads";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
var MessageEvent = class extends Event {
|
|
5
|
+
data;
|
|
6
|
+
constructor(data) {
|
|
7
|
+
super("message");
|
|
8
|
+
this.data = data;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var ErrorEvent = class extends Event {
|
|
12
|
+
error;
|
|
13
|
+
constructor(error) {
|
|
14
|
+
super("error");
|
|
15
|
+
this.error = error;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var CloseEvent = class extends Event {
|
|
19
|
+
code;
|
|
20
|
+
constructor(code) {
|
|
21
|
+
super("close");
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var WORKER_WRAPPER_CODE = `import{parentPort as p}from"worker_threads";const l=new Set();globalThis.onmessage=null;globalThis.onmessageerror=null;globalThis.postMessage=(d,t)=>t?.length?p.postMessage(d,t):p.postMessage(d);globalThis.self=globalThis;globalThis.addEventListener=(t,f)=>t==="message"&&l.add(f);globalThis.removeEventListener=(t,f)=>t==="message"&&l.delete(f);p.on("message",d=>{const e={data:d,type:"message"};globalThis.onmessage?.(e);l.forEach(f=>f(e))});const u=process.env.WORKER_SCRIPT_URL;if(u)await import(u);else throw Error("WORKER_SCRIPT_URL not set");`;
|
|
26
|
+
var WORKER_WRAPPER_DATA_URL = new URL(
|
|
27
|
+
`data:text/javascript,${encodeURIComponent(WORKER_WRAPPER_CODE)}`
|
|
28
|
+
);
|
|
6
29
|
var Worker = class {
|
|
7
|
-
nodeWorker;
|
|
8
|
-
messageListeners
|
|
9
|
-
errorListeners
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
#nodeWorker;
|
|
31
|
+
#messageListeners;
|
|
32
|
+
#errorListeners;
|
|
33
|
+
#messageerrorListeners;
|
|
34
|
+
#closeListeners;
|
|
35
|
+
// Web Worker standard properties
|
|
36
|
+
onmessage;
|
|
37
|
+
onerror;
|
|
38
|
+
onmessageerror;
|
|
39
|
+
onclose;
|
|
40
|
+
constructor(scriptURL, _options) {
|
|
41
|
+
this.#messageListeners = /* @__PURE__ */ new Set();
|
|
42
|
+
this.#errorListeners = /* @__PURE__ */ new Set();
|
|
43
|
+
this.#messageerrorListeners = /* @__PURE__ */ new Set();
|
|
44
|
+
this.#closeListeners = /* @__PURE__ */ new Set();
|
|
45
|
+
this.onmessage = null;
|
|
46
|
+
this.onerror = null;
|
|
47
|
+
this.onmessageerror = null;
|
|
48
|
+
this.onclose = null;
|
|
49
|
+
const scriptURLString = scriptURL.toString();
|
|
50
|
+
let workerScriptURL = scriptURLString;
|
|
51
|
+
if (!scriptURLString.startsWith("file://") && !scriptURLString.startsWith("data:")) {
|
|
52
|
+
if (scriptURLString.startsWith("./") || scriptURLString.startsWith("../")) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Relative paths are not supported. Use new Worker(new URL('./worker.js', import.meta.url)) instead."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
workerScriptURL = `file://${scriptURLString}`;
|
|
58
|
+
}
|
|
59
|
+
this.#nodeWorker = new NodeWorker(WORKER_WRAPPER_DATA_URL, {
|
|
60
|
+
...{ type: "module" },
|
|
16
61
|
env: {
|
|
62
|
+
// eslint-disable-next-line no-restricted-properties -- Workers inherit parent env
|
|
17
63
|
...process.env,
|
|
18
|
-
|
|
64
|
+
..._options?.env,
|
|
65
|
+
WORKER_SCRIPT_URL: workerScriptURL
|
|
19
66
|
}
|
|
20
67
|
});
|
|
21
|
-
this
|
|
22
|
-
|
|
23
|
-
|
|
68
|
+
this.#setupEventForwarding();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Report a close event when the worker exits
|
|
72
|
+
*/
|
|
73
|
+
#reportClose(code) {
|
|
74
|
+
const event = new CloseEvent(code);
|
|
75
|
+
if (this.onclose) {
|
|
76
|
+
this.onclose(event);
|
|
77
|
+
}
|
|
78
|
+
this.#closeListeners.forEach((listener) => {
|
|
79
|
+
listener(event);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Report an error through the error event mechanism
|
|
84
|
+
*/
|
|
85
|
+
#reportError(error) {
|
|
86
|
+
const event = new ErrorEvent(error);
|
|
87
|
+
if (this.onerror) {
|
|
88
|
+
this.onerror(event);
|
|
89
|
+
}
|
|
90
|
+
this.#errorListeners.forEach((listener) => {
|
|
91
|
+
listener(event);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Set up event forwarding from Node.js Worker to Web Worker API
|
|
96
|
+
*/
|
|
97
|
+
#setupEventForwarding() {
|
|
98
|
+
this.#nodeWorker.on("message", (data) => {
|
|
99
|
+
const event = new MessageEvent(data);
|
|
100
|
+
if (this.onmessage) {
|
|
24
101
|
try {
|
|
25
|
-
|
|
102
|
+
this.onmessage(event);
|
|
26
103
|
} catch (error) {
|
|
27
|
-
|
|
104
|
+
this.#reportError(error);
|
|
28
105
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
this.nodeWorker.on("error", (error) => {
|
|
32
|
-
const event = { error, type: "error" };
|
|
33
|
-
this.errorListeners.forEach((listener) => {
|
|
106
|
+
}
|
|
107
|
+
this.#messageListeners.forEach((listener) => {
|
|
34
108
|
try {
|
|
35
109
|
listener(event);
|
|
36
|
-
} catch (
|
|
37
|
-
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.#reportError(error);
|
|
38
112
|
}
|
|
39
113
|
});
|
|
40
114
|
});
|
|
115
|
+
this.#nodeWorker.on("error", (error) => {
|
|
116
|
+
this.#reportError(error);
|
|
117
|
+
});
|
|
118
|
+
this.#nodeWorker.on("messageerror", (data) => {
|
|
119
|
+
const event = new MessageEvent(data);
|
|
120
|
+
if (this.onmessageerror) {
|
|
121
|
+
this.onmessageerror(event);
|
|
122
|
+
}
|
|
123
|
+
this.#messageerrorListeners.forEach((listener) => {
|
|
124
|
+
listener(event);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
this.#nodeWorker.on("exit", (code) => {
|
|
128
|
+
this.#reportClose(code);
|
|
129
|
+
});
|
|
41
130
|
}
|
|
42
131
|
/**
|
|
43
132
|
* Send a message to the worker
|
|
44
133
|
*/
|
|
45
134
|
postMessage(message, transfer) {
|
|
46
135
|
if (transfer && transfer.length > 0) {
|
|
47
|
-
|
|
136
|
+
this.#nodeWorker.postMessage(message, transfer);
|
|
137
|
+
} else {
|
|
138
|
+
this.#nodeWorker.postMessage(message);
|
|
48
139
|
}
|
|
49
|
-
this.nodeWorker.postMessage(message);
|
|
50
140
|
}
|
|
51
141
|
addEventListener(type, listener) {
|
|
52
142
|
if (type === "message") {
|
|
53
|
-
this
|
|
143
|
+
this.#messageListeners.add(listener);
|
|
54
144
|
} else if (type === "error") {
|
|
55
|
-
this
|
|
56
|
-
} else {
|
|
57
|
-
|
|
145
|
+
this.#errorListeners.add(listener);
|
|
146
|
+
} else if (type === "messageerror") {
|
|
147
|
+
this.#messageerrorListeners.add(
|
|
148
|
+
listener
|
|
149
|
+
);
|
|
150
|
+
} else if (type === "close") {
|
|
151
|
+
this.#closeListeners.add(listener);
|
|
58
152
|
}
|
|
59
153
|
}
|
|
60
154
|
removeEventListener(type, listener) {
|
|
61
155
|
if (type === "message") {
|
|
62
|
-
this
|
|
156
|
+
this.#messageListeners.delete(listener);
|
|
63
157
|
} else if (type === "error") {
|
|
64
|
-
this
|
|
158
|
+
this.#errorListeners.delete(listener);
|
|
159
|
+
} else if (type === "messageerror") {
|
|
160
|
+
this.#messageerrorListeners.delete(
|
|
161
|
+
listener
|
|
162
|
+
);
|
|
163
|
+
} else if (type === "close") {
|
|
164
|
+
this.#closeListeners.delete(listener);
|
|
65
165
|
}
|
|
66
166
|
}
|
|
67
167
|
/**
|
|
68
|
-
* Terminate the worker
|
|
168
|
+
* Terminate the worker (Web Worker standard - returns void, not a promise)
|
|
69
169
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
170
|
+
terminate() {
|
|
171
|
+
this.#nodeWorker.terminate().catch(() => {
|
|
172
|
+
});
|
|
173
|
+
this.#messageListeners.clear();
|
|
174
|
+
this.#errorListeners.clear();
|
|
175
|
+
this.#messageerrorListeners.clear();
|
|
176
|
+
this.#closeListeners.clear();
|
|
177
|
+
this.onmessage = null;
|
|
178
|
+
this.onerror = null;
|
|
179
|
+
this.onmessageerror = null;
|
|
180
|
+
this.onclose = null;
|
|
81
181
|
}
|
|
82
182
|
};
|
|
83
183
|
var src_default = Worker;
|
|
84
184
|
export {
|
|
185
|
+
CloseEvent,
|
|
186
|
+
ErrorEvent,
|
|
187
|
+
MessageEvent,
|
|
85
188
|
Worker,
|
|
86
189
|
src_default as default
|
|
87
190
|
};
|
package/src/worker-wrapper.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// src/worker-wrapper.js
|
|
2
|
-
import { parentPort } from "worker_threads";
|
|
3
|
-
globalThis.onmessage = null;
|
|
4
|
-
globalThis.postMessage = (data) => parentPort.postMessage(data);
|
|
5
|
-
parentPort.on("message", (data) => {
|
|
6
|
-
if (globalThis.onmessage) {
|
|
7
|
-
globalThis.onmessage({ data, type: "message" });
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
var WORKER_SCRIPT_URL = process.env.WORKER_SCRIPT_URL;
|
|
11
|
-
if (WORKER_SCRIPT_URL) {
|
|
12
|
-
await import(WORKER_SCRIPT_URL);
|
|
13
|
-
} else {
|
|
14
|
-
throw new Error("WORKER_SCRIPT_URL environment variable not set");
|
|
15
|
-
}
|