@xylabs/threads 4.3.1 → 4.3.3
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 +20 -2
- package/dist/esm/master/implementation.node.js +74 -5
- package/dist/esm/worker/implementation.js +2 -1
- package/dist/master/implementation.node.js +74 -5
- package/dist/worker/implementation.js +2 -1
- package/package.json +11 -3
- package/src/master/implementation.node.ts +103 -8
- package/src/master/spawn.ts +1 -1
- package/src/types/master.ts +2 -2
- package/src/worker/implementation.tiny-worker.ts +2 -0
- package/src/worker/implementation.ts +2 -1
- package/test-tooling/webpack/webpack.node.config.js +1 -0
- package/types/tiny-worker.d.ts +4 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
Offload CPU-intensive tasks to worker threads in node.js, web browsers and electron using one uniform API.
|
|
13
13
|
|
|
14
|
-
Uses web workers in the browser
|
|
14
|
+
Uses web workers in the browser, `worker_threads` in node 12+ and [`tiny-worker`](https://github.com/avoidwork/tiny-worker) in node 8 to 11.
|
|
15
15
|
|
|
16
16
|
### Features
|
|
17
17
|
|
|
@@ -28,9 +28,11 @@ You can find the old version 0.12 of threads.js on the [`v0` branch](https://git
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
npm install threads
|
|
31
|
+
npm install threads tiny-worker
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
*You only need to install the `tiny-worker` package to support node.js < 12. It's an optional dependency and used as a fallback if `worker_threads` are not available.*
|
|
35
|
+
|
|
34
36
|
## Platform support
|
|
35
37
|
|
|
36
38
|
<details>
|
|
@@ -71,6 +73,22 @@ Then add it to your `webpack.config.js`:
|
|
|
71
73
|
}
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
#### Node.js bundles
|
|
77
|
+
|
|
78
|
+
If you are using webpack to create a bundle that will be run in node (webpack config `target: "node"`), you also need to specify that the `tiny-worker` package used for node < 12 should not be bundled:
|
|
79
|
+
|
|
80
|
+
```diff
|
|
81
|
+
module.exports = {
|
|
82
|
+
// ...
|
|
83
|
+
+ externals: {
|
|
84
|
+
+ "tiny-worker": "tiny-worker"
|
|
85
|
+
+ }
|
|
86
|
+
// ...
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Make sure that `tiny-worker` is listed in your `package.json` `dependencies` in that case.
|
|
91
|
+
|
|
74
92
|
#### When using TypeScript
|
|
75
93
|
|
|
76
94
|
Note: You'll need to be using Typescript version 4+, as the types generated by threads.js are not supported in Typescript 3.
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.defaultPoolSize = void 0;
|
|
7
7
|
exports.getWorkerImplementation = getWorkerImplementation;
|
|
8
8
|
exports.isWorkerRuntime = isWorkerRuntime;
|
|
9
|
+
const node_events_1 = require("node:events");
|
|
9
10
|
const node_os_1 = require("node:os");
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_url_1 = require("node:url");
|
|
@@ -113,13 +114,76 @@ function initWorkerThreadsWorker() {
|
|
|
113
114
|
default: Worker,
|
|
114
115
|
};
|
|
115
116
|
}
|
|
117
|
+
function initTinyWorker() {
|
|
118
|
+
const TinyWorker = require('tiny-worker');
|
|
119
|
+
let allWorkers = [];
|
|
120
|
+
class Worker extends TinyWorker {
|
|
121
|
+
emitter;
|
|
122
|
+
constructor(scriptPath, options) {
|
|
123
|
+
const resolvedScriptPath = options && options.fromSource
|
|
124
|
+
? null
|
|
125
|
+
: process.platform === 'win32'
|
|
126
|
+
? `file:///${resolveScriptPath(scriptPath).replaceAll('\\', '/')}`
|
|
127
|
+
: resolveScriptPath(scriptPath);
|
|
128
|
+
if (!resolvedScriptPath) {
|
|
129
|
+
const sourceCode = scriptPath;
|
|
130
|
+
super(new Function(sourceCode), [], { esm: true });
|
|
131
|
+
}
|
|
132
|
+
else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
|
|
133
|
+
super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true });
|
|
134
|
+
}
|
|
135
|
+
else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
|
|
136
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), [], { esm: true });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
super(resolvedScriptPath, [], { esm: true });
|
|
140
|
+
}
|
|
141
|
+
allWorkers.push(this);
|
|
142
|
+
this.emitter = new node_events_1.EventEmitter();
|
|
143
|
+
this.onerror = (error) => this.emitter.emit('error', error);
|
|
144
|
+
this.onmessage = (message) => this.emitter.emit('message', message);
|
|
145
|
+
}
|
|
146
|
+
addEventListener(eventName, listener) {
|
|
147
|
+
this.emitter.addListener(eventName, listener);
|
|
148
|
+
}
|
|
149
|
+
removeEventListener(eventName, listener) {
|
|
150
|
+
this.emitter.removeListener(eventName, listener);
|
|
151
|
+
}
|
|
152
|
+
terminate() {
|
|
153
|
+
allWorkers = allWorkers.filter(worker => worker !== this);
|
|
154
|
+
return super.terminate();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const terminateWorkersAndMaster = () => {
|
|
158
|
+
Promise.all(allWorkers.map(worker => worker.terminate())).then(() => process.exit(0), () => process.exit(1));
|
|
159
|
+
allWorkers = [];
|
|
160
|
+
};
|
|
161
|
+
process.on('SIGINT', () => terminateWorkersAndMaster());
|
|
162
|
+
process.on('SIGTERM', () => terminateWorkersAndMaster());
|
|
163
|
+
class BlobWorker extends Worker {
|
|
164
|
+
constructor(blob, options) {
|
|
165
|
+
super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true });
|
|
166
|
+
}
|
|
167
|
+
static fromText(source, options) {
|
|
168
|
+
return new Worker(source, { ...options, fromSource: true });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
blob: BlobWorker,
|
|
173
|
+
default: Worker,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
116
176
|
let implementation;
|
|
177
|
+
let isTinyWorker;
|
|
117
178
|
function selectWorkerImplementation() {
|
|
118
179
|
try {
|
|
180
|
+
isTinyWorker = false;
|
|
119
181
|
return initWorkerThreadsWorker();
|
|
120
182
|
}
|
|
121
183
|
catch {
|
|
122
|
-
|
|
184
|
+
console.debug('Node worker_threads not available. Trying to fall back to tiny-worker polyfill...');
|
|
185
|
+
isTinyWorker = true;
|
|
186
|
+
return initTinyWorker();
|
|
123
187
|
}
|
|
124
188
|
}
|
|
125
189
|
function getWorkerImplementation() {
|
|
@@ -129,8 +193,13 @@ function getWorkerImplementation() {
|
|
|
129
193
|
return implementation;
|
|
130
194
|
}
|
|
131
195
|
function isWorkerRuntime() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
196
|
+
if (isTinyWorker) {
|
|
197
|
+
return self !== undefined && self['postMessage'] ? true : false;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const isMainThread = typeof __non_webpack_require__ === 'function'
|
|
201
|
+
? __non_webpack_require__('worker_threads').isMainThread
|
|
202
|
+
: eval('require')('worker_threads').isMainThread;
|
|
203
|
+
return !isMainThread;
|
|
204
|
+
}
|
|
136
205
|
}
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const implementation_browser_1 = __importDefault(require("./implementation.browser"));
|
|
7
|
+
const implementation_tiny_worker_1 = __importDefault(require("./implementation.tiny-worker"));
|
|
7
8
|
const implementation_worker_threads_1 = __importDefault(require("./implementation.worker_threads"));
|
|
8
9
|
const runningInNode = typeof process !== 'undefined' && process.arch !== 'browser' && 'pid' in process;
|
|
9
10
|
function selectNodeImplementation() {
|
|
@@ -12,7 +13,7 @@ function selectNodeImplementation() {
|
|
|
12
13
|
return implementation_worker_threads_1.default;
|
|
13
14
|
}
|
|
14
15
|
catch {
|
|
15
|
-
|
|
16
|
+
return implementation_tiny_worker_1.default;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
exports.default = runningInNode ? selectNodeImplementation() : implementation_browser_1.default;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.defaultPoolSize = void 0;
|
|
7
7
|
exports.getWorkerImplementation = getWorkerImplementation;
|
|
8
8
|
exports.isWorkerRuntime = isWorkerRuntime;
|
|
9
|
+
const node_events_1 = require("node:events");
|
|
9
10
|
const node_os_1 = require("node:os");
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_url_1 = require("node:url");
|
|
@@ -113,13 +114,76 @@ function initWorkerThreadsWorker() {
|
|
|
113
114
|
default: Worker,
|
|
114
115
|
};
|
|
115
116
|
}
|
|
117
|
+
function initTinyWorker() {
|
|
118
|
+
const TinyWorker = require('tiny-worker');
|
|
119
|
+
let allWorkers = [];
|
|
120
|
+
class Worker extends TinyWorker {
|
|
121
|
+
emitter;
|
|
122
|
+
constructor(scriptPath, options) {
|
|
123
|
+
const resolvedScriptPath = options && options.fromSource
|
|
124
|
+
? null
|
|
125
|
+
: process.platform === 'win32'
|
|
126
|
+
? `file:///${resolveScriptPath(scriptPath).replaceAll('\\', '/')}`
|
|
127
|
+
: resolveScriptPath(scriptPath);
|
|
128
|
+
if (!resolvedScriptPath) {
|
|
129
|
+
const sourceCode = scriptPath;
|
|
130
|
+
super(new Function(sourceCode), [], { esm: true });
|
|
131
|
+
}
|
|
132
|
+
else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
|
|
133
|
+
super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true });
|
|
134
|
+
}
|
|
135
|
+
else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
|
|
136
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), [], { esm: true });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
super(resolvedScriptPath, [], { esm: true });
|
|
140
|
+
}
|
|
141
|
+
allWorkers.push(this);
|
|
142
|
+
this.emitter = new node_events_1.EventEmitter();
|
|
143
|
+
this.onerror = (error) => this.emitter.emit('error', error);
|
|
144
|
+
this.onmessage = (message) => this.emitter.emit('message', message);
|
|
145
|
+
}
|
|
146
|
+
addEventListener(eventName, listener) {
|
|
147
|
+
this.emitter.addListener(eventName, listener);
|
|
148
|
+
}
|
|
149
|
+
removeEventListener(eventName, listener) {
|
|
150
|
+
this.emitter.removeListener(eventName, listener);
|
|
151
|
+
}
|
|
152
|
+
terminate() {
|
|
153
|
+
allWorkers = allWorkers.filter(worker => worker !== this);
|
|
154
|
+
return super.terminate();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const terminateWorkersAndMaster = () => {
|
|
158
|
+
Promise.all(allWorkers.map(worker => worker.terminate())).then(() => process.exit(0), () => process.exit(1));
|
|
159
|
+
allWorkers = [];
|
|
160
|
+
};
|
|
161
|
+
process.on('SIGINT', () => terminateWorkersAndMaster());
|
|
162
|
+
process.on('SIGTERM', () => terminateWorkersAndMaster());
|
|
163
|
+
class BlobWorker extends Worker {
|
|
164
|
+
constructor(blob, options) {
|
|
165
|
+
super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true });
|
|
166
|
+
}
|
|
167
|
+
static fromText(source, options) {
|
|
168
|
+
return new Worker(source, { ...options, fromSource: true });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
blob: BlobWorker,
|
|
173
|
+
default: Worker,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
116
176
|
let implementation;
|
|
177
|
+
let isTinyWorker;
|
|
117
178
|
function selectWorkerImplementation() {
|
|
118
179
|
try {
|
|
180
|
+
isTinyWorker = false;
|
|
119
181
|
return initWorkerThreadsWorker();
|
|
120
182
|
}
|
|
121
183
|
catch {
|
|
122
|
-
|
|
184
|
+
console.debug('Node worker_threads not available. Trying to fall back to tiny-worker polyfill...');
|
|
185
|
+
isTinyWorker = true;
|
|
186
|
+
return initTinyWorker();
|
|
123
187
|
}
|
|
124
188
|
}
|
|
125
189
|
function getWorkerImplementation() {
|
|
@@ -129,8 +193,13 @@ function getWorkerImplementation() {
|
|
|
129
193
|
return implementation;
|
|
130
194
|
}
|
|
131
195
|
function isWorkerRuntime() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
196
|
+
if (isTinyWorker) {
|
|
197
|
+
return self !== undefined && self['postMessage'] ? true : false;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const isMainThread = typeof __non_webpack_require__ === 'function'
|
|
201
|
+
? __non_webpack_require__('worker_threads').isMainThread
|
|
202
|
+
: eval('require')('worker_threads').isMainThread;
|
|
203
|
+
return !isMainThread;
|
|
204
|
+
}
|
|
136
205
|
}
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const implementation_browser_1 = __importDefault(require("./implementation.browser"));
|
|
7
|
+
const implementation_tiny_worker_1 = __importDefault(require("./implementation.tiny-worker"));
|
|
7
8
|
const implementation_worker_threads_1 = __importDefault(require("./implementation.worker_threads"));
|
|
8
9
|
const runningInNode = typeof process !== 'undefined' && process.arch !== 'browser' && 'pid' in process;
|
|
9
10
|
function selectNodeImplementation() {
|
|
@@ -12,7 +13,7 @@ function selectNodeImplementation() {
|
|
|
12
13
|
return implementation_worker_threads_1.default;
|
|
13
14
|
}
|
|
14
15
|
catch {
|
|
15
|
-
|
|
16
|
+
return implementation_tiny_worker_1.default;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
exports.default = runningInNode ? selectNodeImplementation() : implementation_browser_1.default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xylabs/threads",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3",
|
|
4
4
|
"description": "Web workers & worker threads as simple as a function call",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -95,11 +95,12 @@
|
|
|
95
95
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
96
96
|
"@types/chai": "^5.0.0",
|
|
97
97
|
"@types/debug": "^4.1.12",
|
|
98
|
-
"@types/
|
|
98
|
+
"@types/execa": "^2.0.2",
|
|
99
|
+
"@types/node": "^22.8.0",
|
|
99
100
|
"@types/webpack": "^5.28.5",
|
|
100
101
|
"@xylabs/ts-scripts-yarn3": "^4.2.3",
|
|
101
102
|
"ava": "^6.1.3",
|
|
102
|
-
"chai": "^5.1.
|
|
103
|
+
"chai": "^5.1.2",
|
|
103
104
|
"cross-env": "^7.0.3",
|
|
104
105
|
"mocha": "^10.7.3",
|
|
105
106
|
"puppet-run": "^0.11.4",
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
"rimraf": "^5.0.10",
|
|
109
110
|
"rollup": "^4.24.0",
|
|
110
111
|
"threads-plugin": "^1.4.0",
|
|
112
|
+
"tiny-worker": "^2.3.0",
|
|
111
113
|
"ts-loader": "^9.5.1",
|
|
112
114
|
"ts-node": "^10.9.2",
|
|
113
115
|
"typescript": "^5.6.3",
|
|
@@ -116,6 +118,9 @@
|
|
|
116
118
|
"webpack": "^5.95.0",
|
|
117
119
|
"worker-plugin": "^5.0.1"
|
|
118
120
|
},
|
|
121
|
+
"optionalDependencies": {
|
|
122
|
+
"tiny-worker": "^2.3.0"
|
|
123
|
+
},
|
|
119
124
|
"ava": {
|
|
120
125
|
"extensions": [
|
|
121
126
|
"ts"
|
|
@@ -133,12 +138,15 @@
|
|
|
133
138
|
"./dist/esm/master/implementation.js": "./dist/esm/master/implementation.browser.js",
|
|
134
139
|
"./dist/esm/master/implementation.node.js": false,
|
|
135
140
|
"./dist/esm/worker/implementation.js": "./dist/esm/worker/implementation.browser.js",
|
|
141
|
+
"./dist/esm/worker/implementation.tiny-worker.js": false,
|
|
136
142
|
"./dist/esm/worker/implementation.worker_threads.js": false,
|
|
137
143
|
"./dist/master/implementation.js": "./dist/master/implementation.browser.js",
|
|
138
144
|
"./dist/master/implementation.node.js": false,
|
|
139
145
|
"./dist/worker/implementation.js": "./dist/worker/implementation.browser.js",
|
|
146
|
+
"./dist/worker/implementation.tiny-worker.js": false,
|
|
140
147
|
"./dist/worker/implementation.worker_threads.js": false,
|
|
141
148
|
"callsites": false,
|
|
149
|
+
"tiny-worker": false,
|
|
142
150
|
"ts-node": false,
|
|
143
151
|
"ts-node/register": false,
|
|
144
152
|
"worker_threads": false
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
1
2
|
/* eslint-disable import-x/no-internal-modules */
|
|
2
3
|
/* eslint-disable unicorn/no-process-exit */
|
|
3
4
|
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
|
|
4
5
|
/* eslint-disable unicorn/prefer-regexp-test */
|
|
5
6
|
|
|
7
|
+
/* eslint-disable unicorn/prefer-add-event-listener */
|
|
8
|
+
/* eslint-disable unicorn/prefer-event-target */
|
|
6
9
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
10
|
/* eslint-disable unicorn/text-encoding-identifier-case */
|
|
8
11
|
/// <reference lib="dom" />
|
|
9
12
|
|
|
13
|
+
import { EventEmitter } from 'node:events'
|
|
10
14
|
import { cpus } from 'node:os'
|
|
11
15
|
import path from 'node:path'
|
|
12
16
|
import { fileURLToPath } from 'node:url'
|
|
@@ -25,9 +29,10 @@ interface WorkerGlobalScope {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
declare const __non_webpack_require__: typeof require
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
32
|
declare const self: WorkerGlobalScope
|
|
30
33
|
|
|
34
|
+
type WorkerEventName = 'error' | 'message'
|
|
35
|
+
|
|
31
36
|
let tsNodeAvailable: boolean | undefined
|
|
32
37
|
|
|
33
38
|
export const defaultPoolSize = cpus().length
|
|
@@ -162,14 +167,100 @@ function initWorkerThreadsWorker(): ImplementationExport {
|
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
|
|
170
|
+
function initTinyWorker(): ImplementationExport {
|
|
171
|
+
const TinyWorker = require('tiny-worker')
|
|
172
|
+
|
|
173
|
+
let allWorkers: Array<typeof TinyWorker> = []
|
|
174
|
+
|
|
175
|
+
class Worker extends TinyWorker {
|
|
176
|
+
private emitter: EventEmitter
|
|
177
|
+
|
|
178
|
+
constructor(scriptPath: string, options?: ThreadsWorkerOptions & { fromSource?: boolean }) {
|
|
179
|
+
// Need to apply a work-around for Windows or it will choke upon the absolute path
|
|
180
|
+
// (`Error [ERR_INVALID_PROTOCOL]: Protocol 'c:' not supported`)
|
|
181
|
+
const resolvedScriptPath
|
|
182
|
+
= options && options.fromSource
|
|
183
|
+
? null
|
|
184
|
+
: process.platform === 'win32'
|
|
185
|
+
? `file:///${resolveScriptPath(scriptPath).replaceAll('\\', '/')}`
|
|
186
|
+
: resolveScriptPath(scriptPath)
|
|
187
|
+
|
|
188
|
+
if (!resolvedScriptPath) {
|
|
189
|
+
// `options.fromSource` is true
|
|
190
|
+
const sourceCode = scriptPath
|
|
191
|
+
super(new Function(sourceCode), [], { esm: true })
|
|
192
|
+
} else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
|
|
193
|
+
super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true })
|
|
194
|
+
} else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
|
|
195
|
+
// See <https://github.com/andywer/threads-plugin/issues/17>
|
|
196
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), [], { esm: true })
|
|
197
|
+
} else {
|
|
198
|
+
super(resolvedScriptPath, [], { esm: true })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
allWorkers.push(this)
|
|
202
|
+
|
|
203
|
+
this.emitter = new EventEmitter()
|
|
204
|
+
this.onerror = (error: Error) => this.emitter.emit('error', error)
|
|
205
|
+
this.onmessage = (message: MessageEvent) => this.emitter.emit('message', message)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
addEventListener(eventName: WorkerEventName, listener: EventListener) {
|
|
209
|
+
this.emitter.addListener(eventName, listener)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
removeEventListener(eventName: WorkerEventName, listener: EventListener) {
|
|
213
|
+
this.emitter.removeListener(eventName, listener)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
terminate() {
|
|
217
|
+
allWorkers = allWorkers.filter(worker => worker !== this)
|
|
218
|
+
return super.terminate()
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const terminateWorkersAndMaster = () => {
|
|
223
|
+
// we should terminate all workers and then gracefully shutdown self process
|
|
224
|
+
Promise.all(allWorkers.map(worker => worker.terminate())).then(
|
|
225
|
+
() => process.exit(0),
|
|
226
|
+
() => process.exit(1),
|
|
227
|
+
)
|
|
228
|
+
allWorkers = []
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Take care to not leave orphaned processes behind
|
|
232
|
+
// See <https://github.com/avoidwork/tiny-worker#faq>
|
|
233
|
+
process.on('SIGINT', () => terminateWorkersAndMaster())
|
|
234
|
+
process.on('SIGTERM', () => terminateWorkersAndMaster())
|
|
235
|
+
|
|
236
|
+
class BlobWorker extends Worker {
|
|
237
|
+
constructor(blob: Uint8Array, options?: ThreadsWorkerOptions) {
|
|
238
|
+
super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true })
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation {
|
|
242
|
+
return new Worker(source, { ...options, fromSource: true }) as any
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
blob: BlobWorker as any,
|
|
248
|
+
default: Worker as any,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
165
252
|
let implementation: ImplementationExport
|
|
253
|
+
let isTinyWorker: boolean
|
|
166
254
|
|
|
167
255
|
function selectWorkerImplementation(): ImplementationExport {
|
|
168
256
|
try {
|
|
257
|
+
isTinyWorker = false
|
|
169
258
|
return initWorkerThreadsWorker()
|
|
170
259
|
} catch {
|
|
171
260
|
// tslint:disable-next-line no-console
|
|
172
|
-
|
|
261
|
+
console.debug('Node worker_threads not available. Trying to fall back to tiny-worker polyfill...')
|
|
262
|
+
isTinyWorker = true
|
|
263
|
+
return initTinyWorker()
|
|
173
264
|
}
|
|
174
265
|
}
|
|
175
266
|
|
|
@@ -181,10 +272,14 @@ export function getWorkerImplementation(): ImplementationExport {
|
|
|
181
272
|
}
|
|
182
273
|
|
|
183
274
|
export function isWorkerRuntime() {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
275
|
+
if (isTinyWorker) {
|
|
276
|
+
return self !== undefined && self['postMessage'] ? true : false
|
|
277
|
+
} else {
|
|
278
|
+
// Webpack hack
|
|
279
|
+
const isMainThread
|
|
280
|
+
= typeof __non_webpack_require__ === 'function'
|
|
281
|
+
? __non_webpack_require__('worker_threads').isMainThread
|
|
282
|
+
: eval('require')('worker_threads').isMainThread
|
|
283
|
+
return !isMainThread
|
|
284
|
+
}
|
|
190
285
|
}
|
package/src/master/spawn.ts
CHANGED
|
@@ -138,7 +138,7 @@ function setPrivateThreadProps<T>(
|
|
|
138
138
|
* abstraction layer to provide the transparent API and verifies that
|
|
139
139
|
* the worker has initialized successfully.
|
|
140
140
|
*
|
|
141
|
-
* @param worker Instance of `Worker`. Either a web worker or `
|
|
141
|
+
* @param worker Instance of `Worker`. Either a web worker, `worker_threads` worker or `tiny-worker` worker.
|
|
142
142
|
* @param [options]
|
|
143
143
|
* @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
|
|
144
144
|
*/
|
package/src/types/master.ts
CHANGED
|
@@ -66,10 +66,10 @@ export type Thread = AnyFunctionThread | AnyModuleThread
|
|
|
66
66
|
|
|
67
67
|
export type TransferList = Transferable[]
|
|
68
68
|
|
|
69
|
-
/** Worker instance. Either a web worker or a node.js Worker provided by `worker_threads`. */
|
|
69
|
+
/** Worker instance. Either a web worker or a node.js Worker provided by `worker_threads` or `tiny-worker`. */
|
|
70
70
|
export interface Worker extends EventTarget {
|
|
71
71
|
postMessage(value: any, transferList?: TransferList): void
|
|
72
|
-
/** In nodejs 10+ return type is Promise while in browser return type is void */
|
|
72
|
+
/** In nodejs 10+ return type is Promise while with tiny-worker and in browser return type is void */
|
|
73
73
|
terminate(callback?: (error?: Error, exitCode?: number) => void): void | Promise<number>
|
|
74
74
|
}
|
|
75
75
|
export interface ThreadsWorkerOptions extends WorkerOptions {
|
|
@@ -34,6 +34,8 @@ const messageHandlers = new Set<(data: any) => void>()
|
|
|
34
34
|
|
|
35
35
|
const subscribeToMasterMessages: AbstractedWorkerAPI['subscribeToMasterMessages'] = function subscribeToMasterMessages(onMessage) {
|
|
36
36
|
if (!muxingHandlerSetUp) {
|
|
37
|
+
// We have one multiplexing message handler as tiny-worker's
|
|
38
|
+
// addEventListener() only allows you to set a single message handler
|
|
37
39
|
self.addEventListener('message', ((event: MessageEvent) => {
|
|
38
40
|
for (const handler of messageHandlers) handler(event.data)
|
|
39
41
|
}) as EventListener)
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { AbstractedWorkerAPI } from '../types/worker'
|
|
8
8
|
import WebWorkerImplementation from './implementation.browser'
|
|
9
|
+
import TinyWorkerImplementation from './implementation.tiny-worker'
|
|
9
10
|
import WorkerThreadsImplementation from './implementation.worker_threads'
|
|
10
11
|
|
|
11
12
|
const runningInNode = typeof process !== 'undefined' && (process.arch as string) !== 'browser' && 'pid' in process
|
|
@@ -15,7 +16,7 @@ function selectNodeImplementation(): AbstractedWorkerAPI {
|
|
|
15
16
|
WorkerThreadsImplementation.testImplementation()
|
|
16
17
|
return WorkerThreadsImplementation
|
|
17
18
|
} catch {
|
|
18
|
-
|
|
19
|
+
return TinyWorkerImplementation
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|