@xylabs/threads 3.0.4
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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/common.d.ts +4 -0
- package/dist/common.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +27 -0
- package/dist/master/get-bundle-url.browser.d.ts +3 -0
- package/dist/master/get-bundle-url.browser.js +29 -0
- package/dist/master/implementation.browser.d.ts +4 -0
- package/dist/master/implementation.browser.js +69 -0
- package/dist/master/implementation.d.ts +6 -0
- package/dist/master/implementation.js +41 -0
- package/dist/master/implementation.node.d.ts +5 -0
- package/dist/master/implementation.node.js +255 -0
- package/dist/master/index.d.ts +13 -0
- package/dist/master/index.js +16 -0
- package/dist/master/invocation-proxy.d.ts +3 -0
- package/dist/master/invocation-proxy.js +130 -0
- package/dist/master/pool-types.d.ts +65 -0
- package/dist/master/pool-types.js +15 -0
- package/dist/master/pool.d.ts +90 -0
- package/dist/master/pool.js +281 -0
- package/dist/master/register.d.ts +1 -0
- package/dist/master/register.js +12 -0
- package/dist/master/spawn.d.ts +20 -0
- package/dist/master/spawn.js +130 -0
- package/dist/master/thread.d.ts +12 -0
- package/dist/master/thread.js +22 -0
- package/dist/observable-promise.d.ts +38 -0
- package/dist/observable-promise.js +156 -0
- package/dist/observable.d.ts +19 -0
- package/dist/observable.js +43 -0
- package/dist/ponyfills.d.ts +8 -0
- package/dist/ponyfills.js +22 -0
- package/dist/promise.d.ts +5 -0
- package/dist/promise.js +29 -0
- package/dist/serializers.d.ts +16 -0
- package/dist/serializers.js +41 -0
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.js +8 -0
- package/dist/transferable.d.ts +42 -0
- package/dist/transferable.js +28 -0
- package/dist/types/master.d.ts +99 -0
- package/dist/types/master.js +14 -0
- package/dist/types/messages.d.ts +62 -0
- package/dist/types/messages.js +20 -0
- package/dist/types/worker.d.ts +11 -0
- package/dist/types/worker.js +2 -0
- package/dist/worker/bundle-entry.d.ts +1 -0
- package/dist/worker/bundle-entry.js +27 -0
- package/dist/worker/implementation.browser.d.ts +7 -0
- package/dist/worker/implementation.browser.js +28 -0
- package/dist/worker/implementation.d.ts +3 -0
- package/dist/worker/implementation.js +24 -0
- package/dist/worker/implementation.tiny-worker.d.ts +7 -0
- package/dist/worker/implementation.tiny-worker.js +38 -0
- package/dist/worker/implementation.worker_threads.d.ts +8 -0
- package/dist/worker/implementation.worker_threads.js +42 -0
- package/dist/worker/index.d.ts +13 -0
- package/dist/worker/index.js +195 -0
- package/dist/worker_threads.d.ts +8 -0
- package/dist/worker_threads.js +17 -0
- package/dist-esm/common.js +12 -0
- package/dist-esm/index.js +6 -0
- package/dist-esm/master/get-bundle-url.browser.js +25 -0
- package/dist-esm/master/implementation.browser.js +64 -0
- package/dist-esm/master/implementation.js +15 -0
- package/dist-esm/master/implementation.node.js +224 -0
- package/dist-esm/master/index.js +9 -0
- package/dist-esm/master/invocation-proxy.js +122 -0
- package/dist-esm/master/pool-types.js +12 -0
- package/dist-esm/master/pool.js +273 -0
- package/dist-esm/master/register.js +10 -0
- package/dist-esm/master/spawn.js +123 -0
- package/dist-esm/master/thread.js +19 -0
- package/dist-esm/observable-promise.js +152 -0
- package/dist-esm/observable.js +38 -0
- package/dist-esm/ponyfills.js +18 -0
- package/dist-esm/promise.js +25 -0
- package/dist-esm/serializers.js +37 -0
- package/dist-esm/symbols.js +5 -0
- package/dist-esm/transferable.js +23 -0
- package/dist-esm/types/master.js +11 -0
- package/dist-esm/types/messages.js +17 -0
- package/dist-esm/types/worker.js +1 -0
- package/dist-esm/worker/bundle-entry.js +11 -0
- package/dist-esm/worker/implementation.browser.js +26 -0
- package/dist-esm/worker/implementation.js +19 -0
- package/dist-esm/worker/implementation.tiny-worker.js +36 -0
- package/dist-esm/worker/implementation.worker_threads.js +37 -0
- package/dist-esm/worker/index.js +186 -0
- package/dist-esm/worker_threads.js +14 -0
- package/index.mjs +11 -0
- package/observable.d.ts +2 -0
- package/observable.js +3 -0
- package/observable.mjs +5 -0
- package/package.json +141 -0
- package/register.d.ts +3 -0
- package/register.js +3 -0
- package/register.mjs +2 -0
- package/rollup.config.js +16 -0
- package/src/common.ts +16 -0
- package/src/index.ts +8 -0
- package/src/master/get-bundle-url.browser.ts +31 -0
- package/src/master/implementation.browser.ts +80 -0
- package/src/master/implementation.node.ts +284 -0
- package/src/master/implementation.ts +21 -0
- package/src/master/index.ts +20 -0
- package/src/master/invocation-proxy.ts +146 -0
- package/src/master/pool-types.ts +83 -0
- package/src/master/pool.ts +391 -0
- package/src/master/register.ts +10 -0
- package/src/master/spawn.ts +172 -0
- package/src/master/thread.ts +26 -0
- package/src/observable-promise.ts +181 -0
- package/src/observable.ts +43 -0
- package/src/ponyfills.ts +31 -0
- package/src/promise.ts +26 -0
- package/src/serializers.ts +67 -0
- package/src/symbols.ts +5 -0
- package/src/transferable.ts +68 -0
- package/src/types/master.ts +130 -0
- package/src/types/messages.ts +81 -0
- package/src/types/worker.ts +14 -0
- package/src/worker/bundle-entry.ts +10 -0
- package/src/worker/implementation.browser.ts +40 -0
- package/src/worker/implementation.tiny-worker.ts +52 -0
- package/src/worker/implementation.ts +23 -0
- package/src/worker/implementation.worker_threads.ts +50 -0
- package/src/worker/index.ts +228 -0
- package/src/worker_threads.ts +28 -0
- package/test/lib/serialization.ts +38 -0
- package/test/observable-promise.test.ts +189 -0
- package/test/observable.test.ts +86 -0
- package/test/pool.test.ts +173 -0
- package/test/serialization.test.ts +21 -0
- package/test/spawn.chromium.mocha.ts +49 -0
- package/test/spawn.test.ts +71 -0
- package/test/streaming.test.ts +27 -0
- package/test/transferables.test.ts +69 -0
- package/test/workers/arraybuffer-xor.ts +11 -0
- package/test/workers/count-to-five.ts +13 -0
- package/test/workers/counter.ts +20 -0
- package/test/workers/faulty-function.ts +6 -0
- package/test/workers/hello-world.ts +6 -0
- package/test/workers/increment.ts +9 -0
- package/test/workers/minmax.ts +25 -0
- package/test/workers/serialization.ts +12 -0
- package/test/workers/top-level-throw.ts +1 -0
- package/test-tooling/rollup/app.js +20 -0
- package/test-tooling/rollup/rollup.config.ts +15 -0
- package/test-tooling/rollup/rollup.test.ts +44 -0
- package/test-tooling/rollup/worker.js +7 -0
- package/test-tooling/tsconfig/minimal-tsconfig.test.ts +7 -0
- package/test-tooling/tsconfig/minimal.ts +10 -0
- package/test-tooling/webpack/addition-worker.ts +10 -0
- package/test-tooling/webpack/app-with-inlined-worker.ts +29 -0
- package/test-tooling/webpack/app.ts +58 -0
- package/test-tooling/webpack/pool-worker.ts +6 -0
- package/test-tooling/webpack/raw-loader.d.ts +4 -0
- package/test-tooling/webpack/webpack.chromium.mocha.ts +21 -0
- package/test-tooling/webpack/webpack.node.config.js +38 -0
- package/test-tooling/webpack/webpack.test.ts +90 -0
- package/test-tooling/webpack/webpack.web.config.js +35 -0
- package/types/is-observable.d.ts +7 -0
- package/types/tiny-worker.d.ts +4 -0
- package/types/webworker.d.ts +9 -0
- package/worker.d.ts +2 -0
- package/worker.js +3 -0
- package/worker.mjs +7 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable import/no-internal-modules */
|
|
2
|
+
import * as Threads from './dist/index.js'
|
|
3
|
+
|
|
4
|
+
export const registerSerializer = Threads.registerSerializer
|
|
5
|
+
export const spawn = Threads.spawn
|
|
6
|
+
export const BlobWorker = Threads.BlobWorker
|
|
7
|
+
export const DefaultSerializer = Threads.DefaultSerializer
|
|
8
|
+
export const Pool = Threads.Pool
|
|
9
|
+
export const Thread = Threads.Thread
|
|
10
|
+
export const Transfer = Threads.Transfer
|
|
11
|
+
export const Worker = Threads.Worker
|
package/observable.d.ts
ADDED
package/observable.js
ADDED
package/observable.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xylabs/threads",
|
|
3
|
+
"version": "3.0.4",
|
|
4
|
+
"description": "Web workers & worker threads as simple as a function call",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist-esm/index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"package-compile": "yarn build",
|
|
10
|
+
"clean": "rimraf dist/ dist-esm/",
|
|
11
|
+
"dev": "npm run clean && tsc -p tsconfig.json --watch",
|
|
12
|
+
"build": "npm run clean && npm run build:cjs && npm run build:es",
|
|
13
|
+
"build:cjs": "tsc -p tsconfig.json",
|
|
14
|
+
"build:es": "tsc -p tsconfig-esm.json",
|
|
15
|
+
"postbuild": "npm run bundle",
|
|
16
|
+
"bundle": "rollup -c -f umd --file=bundle/worker.js --name=threads --silent -- dist-esm/worker/bundle-entry.js",
|
|
17
|
+
"test": "npm run test:library && npm run test:tooling && npm run test:puppeteer:basic && npm run test:puppeteer:webpack",
|
|
18
|
+
"test:library": "cross-env TS_NODE_FILES=true ava ./test/**/*.test.ts",
|
|
19
|
+
"test:tooling": "cross-env TS_NODE_FILES=true ava ./test-tooling/**/*.test.ts",
|
|
20
|
+
"test:puppeteer:basic": "puppet-run --plugin=mocha --bundle=./test/workers/:workers/ --serve=./bundle/worker.js:/worker.js ./test/*.chromium*.ts",
|
|
21
|
+
"test:puppeteer:webpack": "puppet-run --serve ./test-tooling/webpack/dist/app.web/0.worker.js --serve ./test-tooling/webpack/dist/app.web/1.worker.js --plugin=mocha ./test-tooling/webpack/webpack.chromium.mocha.ts",
|
|
22
|
+
"prepare": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"require": "./dist/index.js",
|
|
27
|
+
"default": "./index.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./observable": {
|
|
30
|
+
"require": "./observable.js",
|
|
31
|
+
"default": "./observable.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./register": {
|
|
34
|
+
"require": "./register.js",
|
|
35
|
+
"default": "./register.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./worker": {
|
|
38
|
+
"require": "./worker.js",
|
|
39
|
+
"default": "./worker.mjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"sideEffects": [
|
|
43
|
+
"./dist*/master/register.js",
|
|
44
|
+
"./dist*/worker/index.js",
|
|
45
|
+
"./register.*js",
|
|
46
|
+
"./worker.*js"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/andywer/threads.js.git"
|
|
51
|
+
},
|
|
52
|
+
"author": "Andy Wermke (https://github.com/andywer)",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/andywer/threads.js/issues"
|
|
55
|
+
},
|
|
56
|
+
"funding": "https://github.com/andywer/threads.js?sponsor=1",
|
|
57
|
+
"homepage": "https://threads.js.org",
|
|
58
|
+
"keywords": [
|
|
59
|
+
"thread",
|
|
60
|
+
"worker",
|
|
61
|
+
"pool",
|
|
62
|
+
"spawn",
|
|
63
|
+
"isomorphic",
|
|
64
|
+
"parallel",
|
|
65
|
+
"observable",
|
|
66
|
+
"worker_threads"
|
|
67
|
+
],
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"callsites": "^3.1.0",
|
|
70
|
+
"debug": "^4.2.0",
|
|
71
|
+
"is-observable": "^2.1.0",
|
|
72
|
+
"observable-fns": "^0.6.1"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@rollup/plugin-commonjs": "^16.0.0",
|
|
76
|
+
"@rollup/plugin-node-resolve": "^10.0.0",
|
|
77
|
+
"@types/chai": "^4.2.14",
|
|
78
|
+
"@types/debug": "^4.1.5",
|
|
79
|
+
"@types/execa": "^2.0.0",
|
|
80
|
+
"@types/node": "^20",
|
|
81
|
+
"@types/webpack": "^4.41.23",
|
|
82
|
+
"ava": "^3.13.0",
|
|
83
|
+
"chai": "^4.2.0",
|
|
84
|
+
"cross-env": "^7.0.2",
|
|
85
|
+
"execa": "^4.0.3",
|
|
86
|
+
"mocha": "^8.2.0",
|
|
87
|
+
"puppet-run": "^0.11.3",
|
|
88
|
+
"puppet-run-plugin-mocha": "^0.10.0-alpha",
|
|
89
|
+
"raw-loader": "^4.0.2",
|
|
90
|
+
"rimraf": "^3.0.2",
|
|
91
|
+
"rollup": "^2.32.1",
|
|
92
|
+
"threads-plugin": "^1.3.3",
|
|
93
|
+
"tiny-worker": "^2.2.0",
|
|
94
|
+
"ts-loader": "^8.0.7",
|
|
95
|
+
"ts-node": "^9.0.0",
|
|
96
|
+
"typescript": "^4.2.3",
|
|
97
|
+
"wavy": "^1.0.4",
|
|
98
|
+
"webpack": "^4.44.2",
|
|
99
|
+
"worker-plugin": "^5.0.0"
|
|
100
|
+
},
|
|
101
|
+
"optionalDependencies": {
|
|
102
|
+
"tiny-worker": ">= 2"
|
|
103
|
+
},
|
|
104
|
+
"ava": {
|
|
105
|
+
"extensions": [
|
|
106
|
+
"ts"
|
|
107
|
+
],
|
|
108
|
+
"files": [
|
|
109
|
+
"./test/**/*.test.ts",
|
|
110
|
+
"./test-tooling/**/*.test.ts"
|
|
111
|
+
],
|
|
112
|
+
"require": [
|
|
113
|
+
"ts-node/register"
|
|
114
|
+
],
|
|
115
|
+
"serial": true
|
|
116
|
+
},
|
|
117
|
+
"browser": {
|
|
118
|
+
"./dist-esm/master/implementation.js": "./dist-esm/master/implementation.browser.js",
|
|
119
|
+
"./dist-esm/master/implementation.node.js": false,
|
|
120
|
+
"./dist-esm/worker/implementation.js": "./dist-esm/worker/implementation.browser.js",
|
|
121
|
+
"./dist-esm/worker/implementation.tiny-worker.js": false,
|
|
122
|
+
"./dist-esm/worker/implementation.worker_threads.js": false,
|
|
123
|
+
"./dist/master/implementation.js": "./dist/master/implementation.browser.js",
|
|
124
|
+
"./dist/master/implementation.node.js": false,
|
|
125
|
+
"./dist/worker/implementation.js": "./dist/worker/implementation.browser.js",
|
|
126
|
+
"./dist/worker/implementation.tiny-worker.js": false,
|
|
127
|
+
"./dist/worker/implementation.worker_threads.js": false,
|
|
128
|
+
"callsites": false,
|
|
129
|
+
"tiny-worker": false,
|
|
130
|
+
"ts-node": false,
|
|
131
|
+
"ts-node/register": false,
|
|
132
|
+
"worker_threads": false
|
|
133
|
+
},
|
|
134
|
+
"files": [
|
|
135
|
+
"dist/**",
|
|
136
|
+
"dist-esm/**",
|
|
137
|
+
"*.js",
|
|
138
|
+
"*.mjs",
|
|
139
|
+
"*.ts"
|
|
140
|
+
]
|
|
141
|
+
}
|
package/register.d.ts
ADDED
package/register.js
ADDED
package/register.mjs
ADDED
package/rollup.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
const commonjs = require('@rollup/plugin-commonjs')
|
|
4
|
+
const { nodeResolve } = require('@rollup/plugin-node-resolve')
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
plugins: [
|
|
8
|
+
nodeResolve({
|
|
9
|
+
browser: true,
|
|
10
|
+
mainFields: ['module', 'main'],
|
|
11
|
+
preferBuiltins: true,
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
commonjs(),
|
|
15
|
+
],
|
|
16
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { DefaultSerializer, extendSerializer, JsonSerializable, Serializer, SerializerImplementation } from './serializers'
|
|
3
|
+
|
|
4
|
+
let registeredSerializer: Serializer<JsonSerializable> = DefaultSerializer
|
|
5
|
+
|
|
6
|
+
export function registerSerializer(serializer: SerializerImplementation<JsonSerializable>) {
|
|
7
|
+
registeredSerializer = extendSerializer(registeredSerializer, serializer)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function deserialize(message: JsonSerializable): any {
|
|
11
|
+
return registeredSerializer.deserialize(message)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function serialize(input: any): JsonSerializable {
|
|
15
|
+
return registeredSerializer.serialize(input)
|
|
16
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* eslint-disable import/no-internal-modules */
|
|
2
|
+
export { registerSerializer } from './common'
|
|
3
|
+
export * from './master/index'
|
|
4
|
+
export { QueuedTask } from './master/pool'
|
|
5
|
+
export { ExposedToThreadType as ExposedAs } from './master/spawn'
|
|
6
|
+
export { DefaultSerializer, JsonSerializable, Serializer, SerializerImplementation } from './serializers'
|
|
7
|
+
export { Transfer, TransferDescriptor } from './transferable'
|
|
8
|
+
export { expose } from './worker/index'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Source: <https://github.com/parcel-bundler/parcel/blob/master/packages/core/parcel-bundler/src/builtins/bundle-url.js>
|
|
2
|
+
|
|
3
|
+
let bundleURL: string | undefined
|
|
4
|
+
|
|
5
|
+
function getBundleURLCached(): string {
|
|
6
|
+
if (!bundleURL) {
|
|
7
|
+
bundleURL = getBundleURL()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return bundleURL
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getBundleURL(): string {
|
|
14
|
+
// Attempt to find the URL of the current script and use that as the base URL
|
|
15
|
+
try {
|
|
16
|
+
throw new Error('getBundleURL failed')
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^\n)]+/g)
|
|
19
|
+
if (matches) {
|
|
20
|
+
return getBaseURL(matches[0])
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return '/'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getBaseURL(url: string): string {
|
|
28
|
+
return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)?\/[^/]+(?:\?.*)?$/, '$1') + '/'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { getBaseURL, getBundleURLCached as getBundleURL }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// tslint:disable max-classes-per-file
|
|
2
|
+
|
|
3
|
+
import { ImplementationExport, ThreadsWorkerOptions } from '../types/master'
|
|
4
|
+
import { getBundleURL } from './get-bundle-url.browser'
|
|
5
|
+
|
|
6
|
+
export const defaultPoolSize = typeof navigator !== 'undefined' && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4
|
|
7
|
+
|
|
8
|
+
const isAbsoluteURL = (value: string) => /^[A-Za-z][\d+.A-Za-z\-]*:/.test(value)
|
|
9
|
+
|
|
10
|
+
function createSourceBlobURL(code: string): string {
|
|
11
|
+
const blob = new Blob([code], { type: 'application/javascript' })
|
|
12
|
+
return URL.createObjectURL(blob)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function selectWorkerImplementation(): ImplementationExport {
|
|
16
|
+
if (typeof Worker === 'undefined') {
|
|
17
|
+
// Might happen on Safari, for instance
|
|
18
|
+
// The idea is to only fail if the constructor is actually used
|
|
19
|
+
return class NoWebWorker {
|
|
20
|
+
constructor() {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"No web worker implementation available. You might have tried to spawn a worker within a worker in a browser that doesn't support workers in workers.",
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
} as unknown as ImplementationExport
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class WebWorker extends Worker {
|
|
29
|
+
constructor(url: string | URL, options?: ThreadsWorkerOptions) {
|
|
30
|
+
if (typeof url === 'string' && options && options._baseURL) {
|
|
31
|
+
url = new URL(url, options._baseURL)
|
|
32
|
+
} else if (typeof url === 'string' && !isAbsoluteURL(url) && /^file:\/\//i.test(getBundleURL())) {
|
|
33
|
+
url = new URL(url, getBundleURL().replace(/\/[^/]+$/, '/'))
|
|
34
|
+
if (options?.CORSWorkaround ?? true) {
|
|
35
|
+
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
typeof url === 'string' &&
|
|
40
|
+
isAbsoluteURL(url) && // Create source code blob loading JS file via `importScripts()`
|
|
41
|
+
// to circumvent worker CORS restrictions
|
|
42
|
+
(options?.CORSWorkaround ?? true)
|
|
43
|
+
) {
|
|
44
|
+
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
|
|
45
|
+
}
|
|
46
|
+
super(url, options)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class BlobWorker extends WebWorker {
|
|
51
|
+
constructor(blob: Blob, options?: ThreadsWorkerOptions) {
|
|
52
|
+
const url = window.URL.createObjectURL(blob)
|
|
53
|
+
super(url, options)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static fromText(source: string, options?: ThreadsWorkerOptions): WebWorker {
|
|
57
|
+
const blob = new window.Blob([source], { type: 'text/javascript' })
|
|
58
|
+
return new BlobWorker(blob, options)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
blob: BlobWorker,
|
|
64
|
+
default: WebWorker,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let implementation: ImplementationExport
|
|
69
|
+
|
|
70
|
+
export function getWorkerImplementation(): ImplementationExport {
|
|
71
|
+
if (!implementation) {
|
|
72
|
+
implementation = selectWorkerImplementation()
|
|
73
|
+
}
|
|
74
|
+
return implementation
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isWorkerRuntime() {
|
|
78
|
+
const isWindowContext = typeof self !== 'undefined' && typeof Window !== 'undefined' && self instanceof Window
|
|
79
|
+
return typeof self !== 'undefined' && self['postMessage'] && !isWindowContext ? true : false
|
|
80
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
|
|
2
|
+
/* eslint-disable unicorn/prefer-regexp-test */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
4
|
+
/* eslint-disable unicorn/prefer-add-event-listener */
|
|
5
|
+
/* eslint-disable unicorn/prefer-event-target */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
+
/* eslint-disable unicorn/text-encoding-identifier-case */
|
|
8
|
+
/* eslint-disable unicorn/no-process-exit */
|
|
9
|
+
/// <reference lib="dom" />
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'node:events'
|
|
12
|
+
import { cpus } from 'node:os'
|
|
13
|
+
import * as path from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
import getCallsites, { CallSite } from 'callsites'
|
|
17
|
+
|
|
18
|
+
import { ImplementationExport, ThreadsWorkerOptions, WorkerImplementation } from '../types/master'
|
|
19
|
+
|
|
20
|
+
interface WorkerGlobalScope {
|
|
21
|
+
addEventListener(eventName: string, listener: (event: Event) => void): void
|
|
22
|
+
postMessage(message: any, transferables?: any[]): void
|
|
23
|
+
removeEventListener(eventName: string, listener: (event: Event) => void): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare const __non_webpack_require__: typeof require
|
|
27
|
+
declare const self: WorkerGlobalScope
|
|
28
|
+
|
|
29
|
+
type WorkerEventName = 'error' | 'message'
|
|
30
|
+
|
|
31
|
+
let tsNodeAvailable: boolean | undefined
|
|
32
|
+
|
|
33
|
+
export const defaultPoolSize = cpus().length
|
|
34
|
+
|
|
35
|
+
function detectTsNode() {
|
|
36
|
+
if (typeof __non_webpack_require__ === 'function') {
|
|
37
|
+
// Webpack build: => No ts-node required or possible
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
if (tsNodeAvailable) {
|
|
41
|
+
return tsNodeAvailable
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
eval('require').resolve('ts-node')
|
|
46
|
+
tsNodeAvailable = true
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
49
|
+
tsNodeAvailable = false
|
|
50
|
+
} else {
|
|
51
|
+
// Re-throw
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return tsNodeAvailable
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createTsNodeModule(scriptPath: string) {
|
|
59
|
+
const content = `
|
|
60
|
+
require("ts-node/register/transpile-only");
|
|
61
|
+
require(${JSON.stringify(scriptPath)});
|
|
62
|
+
`
|
|
63
|
+
return content
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function rebaseScriptPath(scriptPath: string, ignoreRegex: RegExp) {
|
|
67
|
+
const parentCallSite = getCallsites().find((callsite: CallSite) => {
|
|
68
|
+
const filename = callsite.getFileName()
|
|
69
|
+
return Boolean(
|
|
70
|
+
filename && !filename.match(ignoreRegex) && !/[/\\]master[/\\]implementation/.test(filename) && !/^internal\/process/.test(filename),
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const rawCallerPath = parentCallSite ? parentCallSite.getFileName() : null
|
|
75
|
+
let callerPath = rawCallerPath ? rawCallerPath : null
|
|
76
|
+
if (callerPath && callerPath.startsWith('file:')) {
|
|
77
|
+
callerPath = fileURLToPath(callerPath)
|
|
78
|
+
}
|
|
79
|
+
const rebasedScriptPath = callerPath ? path.join(path.dirname(callerPath), scriptPath) : scriptPath
|
|
80
|
+
|
|
81
|
+
return rebasedScriptPath
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveScriptPath(scriptPath: string, baseURL?: string | undefined) {
|
|
85
|
+
const makeRelative = (filePath: string) => {
|
|
86
|
+
// eval() hack is also webpack-related
|
|
87
|
+
return path.isAbsolute(filePath) ? filePath : path.join(baseURL || eval('__dirname'), filePath)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const workerFilePath =
|
|
91
|
+
typeof __non_webpack_require__ === 'function' ?
|
|
92
|
+
__non_webpack_require__.resolve(makeRelative(scriptPath))
|
|
93
|
+
: eval('require').resolve(makeRelative(rebaseScriptPath(scriptPath, /[/\\]worker_threads[/\\]/)))
|
|
94
|
+
|
|
95
|
+
return workerFilePath
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function initWorkerThreadsWorker(): ImplementationExport {
|
|
99
|
+
// Webpack hack
|
|
100
|
+
const NativeWorker =
|
|
101
|
+
typeof __non_webpack_require__ === 'function' ? __non_webpack_require__('worker_threads').Worker : eval('require')('worker_threads').Worker
|
|
102
|
+
|
|
103
|
+
let allWorkers: Array<typeof NativeWorker> = []
|
|
104
|
+
|
|
105
|
+
class Worker extends NativeWorker {
|
|
106
|
+
private mappedEventListeners: WeakMap<EventListener, EventListener>
|
|
107
|
+
|
|
108
|
+
constructor(scriptPath: string, options?: ThreadsWorkerOptions & { fromSource: boolean }) {
|
|
109
|
+
const resolvedScriptPath = options && options.fromSource ? null : resolveScriptPath(scriptPath, (options || {})._baseURL)
|
|
110
|
+
|
|
111
|
+
if (!resolvedScriptPath) {
|
|
112
|
+
// `options.fromSource` is true
|
|
113
|
+
const sourceCode = scriptPath
|
|
114
|
+
super(sourceCode, { ...options, eval: true })
|
|
115
|
+
} else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
|
|
116
|
+
super(createTsNodeModule(resolvedScriptPath), { ...options, eval: true })
|
|
117
|
+
} else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
|
|
118
|
+
// See <https://github.com/andywer/threads-plugin/issues/17>
|
|
119
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), options)
|
|
120
|
+
} else {
|
|
121
|
+
super(resolvedScriptPath, options)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.mappedEventListeners = new WeakMap()
|
|
125
|
+
allWorkers.push(this)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
addEventListener(eventName: string, rawListener: EventListener) {
|
|
129
|
+
const listener = (message: any) => {
|
|
130
|
+
rawListener({ data: message } as any)
|
|
131
|
+
}
|
|
132
|
+
this.mappedEventListeners.set(rawListener, listener)
|
|
133
|
+
this.on(eventName, listener)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
removeEventListener(eventName: string, rawListener: EventListener) {
|
|
137
|
+
const listener = this.mappedEventListeners.get(rawListener) || rawListener
|
|
138
|
+
this.off(eventName, listener)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const terminateWorkersAndMaster = () => {
|
|
143
|
+
// we should terminate all workers and then gracefully shutdown self process
|
|
144
|
+
Promise.all(allWorkers.map((worker) => worker.terminate())).then(
|
|
145
|
+
() => process.exit(0),
|
|
146
|
+
() => process.exit(1),
|
|
147
|
+
)
|
|
148
|
+
allWorkers = []
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Take care to not leave orphaned processes behind. See #147.
|
|
152
|
+
process.on('SIGINT', () => terminateWorkersAndMaster())
|
|
153
|
+
process.on('SIGTERM', () => terminateWorkersAndMaster())
|
|
154
|
+
|
|
155
|
+
class BlobWorker extends Worker {
|
|
156
|
+
constructor(blob: Uint8Array, options?: ThreadsWorkerOptions) {
|
|
157
|
+
super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation {
|
|
161
|
+
return new Worker(source, { ...options, fromSource: true }) as any
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
blob: BlobWorker as any,
|
|
167
|
+
default: Worker as any,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function initTinyWorker(): ImplementationExport {
|
|
172
|
+
const TinyWorker = require('tiny-worker')
|
|
173
|
+
|
|
174
|
+
let allWorkers: Array<typeof TinyWorker> = []
|
|
175
|
+
|
|
176
|
+
class Worker extends TinyWorker {
|
|
177
|
+
private emitter: EventEmitter
|
|
178
|
+
|
|
179
|
+
constructor(scriptPath: string, options?: ThreadsWorkerOptions & { fromSource?: boolean }) {
|
|
180
|
+
// Need to apply a work-around for Windows or it will choke upon the absolute path
|
|
181
|
+
// (`Error [ERR_INVALID_PROTOCOL]: Protocol 'c:' not supported`)
|
|
182
|
+
const resolvedScriptPath =
|
|
183
|
+
options && options.fromSource ? null
|
|
184
|
+
: process.platform === 'win32' ? `file:///${resolveScriptPath(scriptPath).replaceAll('\\', '/')}`
|
|
185
|
+
: resolveScriptPath(scriptPath)
|
|
186
|
+
|
|
187
|
+
if (!resolvedScriptPath) {
|
|
188
|
+
// `options.fromSource` is true
|
|
189
|
+
const sourceCode = scriptPath
|
|
190
|
+
super(new Function(sourceCode), [], { esm: true })
|
|
191
|
+
} else if (/\.tsx?$/i.test(resolvedScriptPath) && detectTsNode()) {
|
|
192
|
+
super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true })
|
|
193
|
+
} else if (/\.asar[/\\]/.test(resolvedScriptPath)) {
|
|
194
|
+
// See <https://github.com/andywer/threads-plugin/issues/17>
|
|
195
|
+
super(resolvedScriptPath.replace(/\.asar([/\\])/, '.asar.unpacked$1'), [], { esm: true })
|
|
196
|
+
} else {
|
|
197
|
+
super(resolvedScriptPath, [], { esm: true })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
allWorkers.push(this)
|
|
201
|
+
|
|
202
|
+
this.emitter = new EventEmitter()
|
|
203
|
+
this.onerror = (error: Error) => this.emitter.emit('error', error)
|
|
204
|
+
this.onmessage = (message: MessageEvent) => this.emitter.emit('message', message)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
addEventListener(eventName: WorkerEventName, listener: EventListener) {
|
|
208
|
+
this.emitter.addListener(eventName, listener)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
removeEventListener(eventName: WorkerEventName, listener: EventListener) {
|
|
212
|
+
this.emitter.removeListener(eventName, listener)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
terminate() {
|
|
216
|
+
allWorkers = allWorkers.filter((worker) => worker !== this)
|
|
217
|
+
return super.terminate()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const terminateWorkersAndMaster = () => {
|
|
222
|
+
// we should terminate all workers and then gracefully shutdown self process
|
|
223
|
+
Promise.all(allWorkers.map((worker) => worker.terminate())).then(
|
|
224
|
+
() => process.exit(0),
|
|
225
|
+
() => process.exit(1),
|
|
226
|
+
)
|
|
227
|
+
allWorkers = []
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Take care to not leave orphaned processes behind
|
|
231
|
+
// See <https://github.com/avoidwork/tiny-worker#faq>
|
|
232
|
+
process.on('SIGINT', () => terminateWorkersAndMaster())
|
|
233
|
+
process.on('SIGTERM', () => terminateWorkersAndMaster())
|
|
234
|
+
|
|
235
|
+
class BlobWorker extends Worker {
|
|
236
|
+
constructor(blob: Uint8Array, options?: ThreadsWorkerOptions) {
|
|
237
|
+
super(Buffer.from(blob).toString('utf-8'), { ...options, fromSource: true })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
static fromText(source: string, options?: ThreadsWorkerOptions): WorkerImplementation {
|
|
241
|
+
return new Worker(source, { ...options, fromSource: true }) as any
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
blob: BlobWorker as any,
|
|
247
|
+
default: Worker as any,
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let implementation: ImplementationExport
|
|
252
|
+
let isTinyWorker: boolean
|
|
253
|
+
|
|
254
|
+
function selectWorkerImplementation(): ImplementationExport {
|
|
255
|
+
try {
|
|
256
|
+
isTinyWorker = false
|
|
257
|
+
return initWorkerThreadsWorker()
|
|
258
|
+
} catch {
|
|
259
|
+
// tslint:disable-next-line no-console
|
|
260
|
+
console.debug('Node worker_threads not available. Trying to fall back to tiny-worker polyfill...')
|
|
261
|
+
isTinyWorker = true
|
|
262
|
+
return initTinyWorker()
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function getWorkerImplementation(): ImplementationExport {
|
|
267
|
+
if (!implementation) {
|
|
268
|
+
implementation = selectWorkerImplementation()
|
|
269
|
+
}
|
|
270
|
+
return implementation
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function isWorkerRuntime() {
|
|
274
|
+
if (isTinyWorker) {
|
|
275
|
+
return self !== undefined && self['postMessage'] ? true : false
|
|
276
|
+
} else {
|
|
277
|
+
// Webpack hack
|
|
278
|
+
const isMainThread =
|
|
279
|
+
typeof __non_webpack_require__ === 'function' ?
|
|
280
|
+
__non_webpack_require__('worker_threads').isMainThread
|
|
281
|
+
: eval('require')('worker_threads').isMainThread
|
|
282
|
+
return !isMainThread
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is only a stub to make './implementation' resolve to the right module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// We alias `src/master/implementation` to `src/master/implementation.browser` for web
|
|
6
|
+
// browsers already in the package.json, so if get here, it's safe to pass-through the
|
|
7
|
+
// node implementation
|
|
8
|
+
|
|
9
|
+
import * as BrowserImplementation from './implementation.browser'
|
|
10
|
+
import * as NodeImplementation from './implementation.node'
|
|
11
|
+
|
|
12
|
+
const runningInNode = typeof process !== 'undefined' && (process.arch as string) !== 'browser' && 'pid' in process
|
|
13
|
+
const implementation = runningInNode ? NodeImplementation : BrowserImplementation
|
|
14
|
+
|
|
15
|
+
/** Default size of pools. Depending on the platform the value might vary from device to device. */
|
|
16
|
+
export const defaultPoolSize = implementation.defaultPoolSize
|
|
17
|
+
|
|
18
|
+
export const getWorkerImplementation = implementation.getWorkerImplementation
|
|
19
|
+
|
|
20
|
+
/** Returns `true` if this code is currently running in a worker. */
|
|
21
|
+
export const isWorkerRuntime = implementation.isWorkerRuntime
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// tslint:disable no-duplicate-imports
|
|
2
|
+
import type { BlobWorker as BlobWorkerClass } from '../types/master'
|
|
3
|
+
import { Worker as WorkerType } from '../types/master'
|
|
4
|
+
import { getWorkerImplementation } from './implementation'
|
|
5
|
+
|
|
6
|
+
export { FunctionThread, ModuleThread } from '../types/master'
|
|
7
|
+
export { Pool } from './pool'
|
|
8
|
+
export { spawn } from './spawn'
|
|
9
|
+
export { Thread } from './thread'
|
|
10
|
+
|
|
11
|
+
export type BlobWorker = typeof BlobWorkerClass
|
|
12
|
+
export type Worker = WorkerType
|
|
13
|
+
|
|
14
|
+
/** Separate class to spawn workers from source code blobs or strings. */
|
|
15
|
+
export const BlobWorker = getWorkerImplementation().blob
|
|
16
|
+
|
|
17
|
+
/** Worker implementation. Either web worker or a node.js Worker class. */
|
|
18
|
+
export const Worker = getWorkerImplementation().default
|
|
19
|
+
|
|
20
|
+
export { isWorkerRuntime } from './implementation'
|