Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support choosing custom worker backend #290

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
086ed70
feat: add createWorker function
aminya Aug 9, 2020
cc1c915
feat: only import the needed function
aminya Aug 9, 2020
5da90ce
feat: use the correct types for WorkerConstructor and workerPath
aminya Aug 9, 2020
a6e3c41
feat: select between tiny and node
aminya Aug 9, 2020
2734f8b
extend ThreadsWorkerOptions in CreateWorkerOptions
aminya Aug 25, 2020
5785b5a
move CreateWorkerOptions to types file
aminya Aug 25, 2020
075e368
add docs for the options
aminya Aug 25, 2020
bcacf38
Use dynamic imports and make createWorker async
aminya Jan 10, 2021
209808a
Add top level files
aminya Jan 10, 2021
9a62784
Rename implementation-node to prevent conflicts with native node addons
aminya Jan 10, 2021
78c4b6a
Add rollup test for createWorker
aminya Jan 10, 2021
a3d1e25
include src in the pack (allow directly importing ts)
aminya Jan 10, 2021
b819c49
Add createWorker to the docs
aminya Jan 10, 2021
eee7976
Mention the possibility of passing WorkerOptions
aminya Jan 10, 2021
3be9a05
Don't export createWorker from index
aminya Mar 27, 2021
c323c34
Make the backend a required argument in createWorker
aminya Mar 27, 2021
d813a9c
Directly import the implementation function
aminya Mar 27, 2021
1dca8f9
Merge remote-tracking branch 'upstream/master' into createWorker
aminya Mar 29, 2021
93eb19e
Move app-createWorker test to test-tooling folder
aminya Mar 29, 2021
faceaca
Add types for root level index
aminya Apr 30, 2021
c4e0a57
Add documentation for createWorker return
aminya Apr 30, 2021
4fdfda9
Add createWorker tests
aminya Apr 30, 2021
5f498fb
Merge branch 'master' into createWorker
aminya Apr 30, 2021
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
1 change: 1 addition & 0 deletions createWorker.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createWorker } from "./dist-esm/createWorker"
1 change: 1 addition & 0 deletions createWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("./dist/createWorker")
1 change: 1 addition & 0 deletions createWorker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { createWorker } from "./dist-esm/createWorker"
27 changes: 27 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,33 @@ const worker = await spawn(BlobWorker.fromText(MyWorker))

Bundle this module and you will obtain a stand-alone bundle that has its worker inlined. This is particularly useful for libraries using threads.js.

### createWorker - select worker backend
`createWorker` allows selecting the worker backend (among web, node, and tiny), and also if you want a blob worker.

The second required argument to the `createWorker` is a string that specifies `backend: 'web' | 'node' | 'tiny'`. The third optional argument is an object that can be used to specify `blob: boolean` or other `WorkerOptions`.

`createWorker` uses dynamic imports to only import the needed implementation, so you can import the needed functions directly to reduce the bundle size.

```js
import { createWorker } from "threads/createWorker"
import { spawn, Thread } from "threads"

async function run() {
const worker = await createWorker("./worker.js", "node")
const add = await spawn(worker)
const result = await add(2, 3)
await Thread.terminate(add)
return result
}

run().then(result => {
console.log(`Result: ${result}`)
}).catch(error => {
console.error(error)
})
```


## TypeScript

### Type-safe workers
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { registerSerializer, spawn, DefaultSerializer, Pool, Thread, Transfer, Worker } from "./src/index.js"
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@
"ts-node/register": false,
"worker_threads": false,
"./dist/master/implementation.js": "./dist/master/implementation.browser.js",
"./dist/master/implementation.node.js": false,
"./dist/master/implementation-node.js": false,
"./dist/worker/implementation.js": "./dist/worker/implementation.browser.js",
"./dist/worker/implementation.tiny-worker.js": false,
"./dist/worker/implementation.worker_threads.js": false,
"./dist-esm/master/implementation.js": "./dist-esm/master/implementation.browser.js",
"./dist-esm/master/implementation.node.js": false,
"./dist-esm/master/implementation-node.js": false,
"./dist-esm/worker/implementation.js": "./dist-esm/worker/implementation.browser.js",
"./dist-esm/worker/implementation.tiny-worker.js": false,
"./dist-esm/worker/implementation.worker_threads.js": false
Expand All @@ -139,6 +139,7 @@
"dist-esm/**",
"*.js",
"*.mjs",
"*.ts"
"*.ts",
"src"
]
}
35 changes: 35 additions & 0 deletions src/createWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
BlobWorker,
CreateWorkerOptions,
WorkerImplementation,
} from "./types/master"

/**
* async function to creat a webworker. This function uses dynamic imports to only import the required implementation
* @param workerPath the path or Blob to the worker code
* @param backend backend for the threads
* @param {CreateWorkerOptions} options an object that can be used to specify `blob: boolean` or other {WorkerOptions}. Defaults to `{}`.
* @returns A web worker, a node.js worker, or a tiny worker class.
*/
export async function createWorker(workerPath: string | Blob, backend: "web" | "node" | "tiny", options: CreateWorkerOptions = {}) {
let WorkerConstructor: typeof WorkerImplementation | typeof BlobWorker
if (backend === "web") {
const { getWorkerImplementation } = await import("./master/implementation.browser")
WorkerConstructor = options.blob ?
getWorkerImplementation().blob :
getWorkerImplementation().default
} else if (backend === "node") {
const { initWorkerThreadsWorker } = await import("./master/implementation-node")
WorkerConstructor = options.blob ?
initWorkerThreadsWorker().blob :
initWorkerThreadsWorker().default
} else if (backend === "tiny") {
const { initTinyWorker } = await import("./master/implementation-node")
WorkerConstructor = options.blob ?
initTinyWorker().blob :
initTinyWorker().default
} else {
throw new Error("The worker backend is not supported.")
}
return new WorkerConstructor(workerPath as string & Blob, options)
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ function resolveScriptPath(scriptPath: string, baseURL?: string | undefined) {
return workerFilePath
}

function initWorkerThreadsWorker(): ImplementationExport {
export function initWorkerThreadsWorker(): ImplementationExport {
isTinyWorker = false
// Webpack hack
const NativeWorker = typeof __non_webpack_require__ === "function"
? __non_webpack_require__("worker_threads").Worker
Expand Down Expand Up @@ -164,7 +165,8 @@ function initWorkerThreadsWorker(): ImplementationExport {
}
}

function initTinyWorker(): ImplementationExport {
export function initTinyWorker(): ImplementationExport {
isTinyWorker = true
const TinyWorker = require("tiny-worker")

let allWorkers: Array<typeof TinyWorker> = []
Expand Down Expand Up @@ -248,21 +250,31 @@ function initTinyWorker(): ImplementationExport {
let implementation: ImplementationExport
let isTinyWorker: boolean

function selectWorkerImplementation(): ImplementationExport {
try {
isTinyWorker = false
function selectWorkerImplementation(selection?: string): ImplementationExport {
if (!selection) {

// automatic version based selection
try {
return initWorkerThreadsWorker()
} catch(error) {
// tslint:disable-next-line no-console
console.debug("Node worker_threads not available. Trying to fall back to tiny-worker polyfill...")
return initTinyWorker()
}

// manual selection
} else if (selection === "node") {
return initWorkerThreadsWorker()
} catch(error) {
// tslint:disable-next-line no-console
console.debug("Node worker_threads not available. Trying to fall back to tiny-worker polyfill...")
isTinyWorker = true
} else if (selection === "tiny") {
return initTinyWorker()
} else {
throw new Error("selection is not supported" + selection)
}
}

export function getWorkerImplementation(): ImplementationExport {
export function getWorkerImplementation(selection?: string): ImplementationExport {
if (!implementation) {
implementation = selectWorkerImplementation()
implementation = selectWorkerImplementation(selection)
}
return implementation
}
Expand Down
3 changes: 1 addition & 2 deletions src/master/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
// We alias `src/master/implementation` to `src/master/implementation.browser` for web
// browsers already in the package.json, so if get here, it's safe to pass-through the
// node implementation

import * as NodeImplementation from "./implementation-node"
import * as BrowserImplementation from "./implementation.browser"
import * as NodeImplementation from "./implementation.node"

const runningInNode = typeof process !== 'undefined' && process.arch !== 'browser' && 'pid' in process
const implementation = runningInNode ? NodeImplementation : BrowserImplementation
Expand Down
5 changes: 5 additions & 0 deletions src/types/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export interface ThreadsWorkerOptions extends WorkerOptions {
CORSWorkaround?: boolean
}

export interface CreateWorkerOptions extends ThreadsWorkerOptions {
/** flag to return a BlobWorker */
blob?: boolean
}

/** Worker implementation. Either web worker or a node.js Worker class. */
export declare class WorkerImplementation extends EventTarget implements Worker {
constructor(path: string, options?: ThreadsWorkerOptions)
Expand Down
17 changes: 17 additions & 0 deletions test-tooling/rollup/app-createWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createWorker } from "../../createWorker.mjs"
import { spawn, Thread } from "../../"

async function run() {
const add = await spawn(await createWorker("./worker.js", "node"))
const result = await add(2, 3)
await Thread.terminate(add)
return result
}

run().then(result => {
console.log(`Result: ${result}`)
puppet.exit(0)
}).catch(error => {
console.error(error)
puppet.exit(1)
})
12 changes: 11 additions & 1 deletion test-tooling/rollup/rollup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ test("can be bundled using rollup", async t => {
...config
})

const appCreateWorkerBundleP = rollup({
input: path.resolve(__dirname, "app-createWorker.js"),
...config
})

const workerBundleP = rollup({
input: path.resolve(__dirname, "worker.js"),
...config
Expand All @@ -22,12 +27,17 @@ test("can be bundled using rollup", async t => {
format: "iife"
})

const appCreateWorkerBundleWriteP = (await appCreateWorkerBundleP).write({
dir: path.resolve(__dirname, "dist"),
format: "es"
})

const workerBundleWriteP = (await workerBundleP).write({
dir: path.resolve(__dirname, "dist"),
format: "iife"
})

await Promise.all([appBundleWriteP, workerBundleWriteP])
await Promise.all([appBundleWriteP, workerBundleWriteP, appCreateWorkerBundleWriteP])

if (process.platform === "win32") {
// Quick-fix for weird Windows issue in CI
Expand Down
27 changes: 27 additions & 0 deletions test/createWorker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import test from "ava"
import { createWorker } from '../src/createWorker'
import { spawn, Thread } from "../src/index"

test("createWorker web", async t => {
const worker = await createWorker("./workers/hello-world", "web")
const helloWorld = await spawn(worker)
t.is(await helloWorld(), "Hello World")
await Thread.terminate(helloWorld)
t.pass()
})

test("createWorker node", async t => {
const worker = await createWorker("./workers/hello-world", "node")
const helloWorld = await spawn(worker)
t.is(await helloWorld(), "Hello World")
await Thread.terminate(helloWorld)
t.pass()
})

test("createWorker tiny", async t => {
const worker = await createWorker("./workers/hello-world", "tiny")
const helloWorld = await spawn(worker)
t.is(await helloWorld(), "Hello World")
await Thread.terminate(helloWorld)
t.pass()
})
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"include": [
"./src/index.ts",
"./src/createWorker.ts",
"./src/observable.ts",
"./src/master/*",
"./src/worker/*",
Expand Down