@xylabs/threads
Version:
Web workers & worker threads as simple as a function call
83 lines (70 loc) • 3.01 kB
text/typescript
/* eslint-disable @stylistic/max-len */
/* eslint-disable import-x/no-internal-modules */
// tslint:disable max-classes-per-file
import type { ImplementationExport, ThreadsWorkerOptions } from '../types/master.ts'
import { getBundleURL } from './get-bundle-url.browser.ts'
export const defaultPoolSize = typeof navigator !== 'undefined' && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4
const isAbsoluteURL = (value: string) => /^[A-Za-z][\d+.A-Za-z\-]*:/.test(value)
function createSourceBlobURL(code: string): string {
const blob = new Blob([code], { type: 'application/javascript' })
return URL.createObjectURL(blob)
}
function selectWorkerImplementation(): ImplementationExport {
if (typeof Worker === 'undefined') {
// Might happen on Safari, for instance
// The idea is to only fail if the constructor is actually used
return class NoWebWorker {
constructor() {
throw new Error(
"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.",
)
}
} as unknown as ImplementationExport
}
class WebWorker extends Worker {
constructor(url: string | URL, options?: ThreadsWorkerOptions) {
if (typeof url === 'string' && options && options._baseURL) {
url = new URL(url, options._baseURL)
} else if (typeof url === 'string' && !isAbsoluteURL(url) && /^file:\/\//i.test(getBundleURL())) {
url = new URL(url, getBundleURL().replace(/\/[^/]+$/, '/'))
if (options?.CORSWorkaround ?? true) {
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
}
}
if (
typeof url === 'string'
&& isAbsoluteURL(url) // Create source code blob loading JS file via `importScripts()`
// to circumvent worker CORS restrictions
&& (options?.CORSWorkaround ?? true)
) {
url = createSourceBlobURL(`importScripts(${JSON.stringify(url)});`)
}
super(url, options)
}
}
class BlobWorker extends WebWorker {
constructor(blob: Blob, options?: ThreadsWorkerOptions) {
const url = globalThis.URL.createObjectURL(blob)
super(url, options)
}
static fromText(source: string, options?: ThreadsWorkerOptions): WebWorker {
const blob = new globalThis.Blob([source], { type: 'text/javascript' })
return new BlobWorker(blob, options)
}
}
return {
blob: BlobWorker,
default: WebWorker,
}
}
let implementation: ImplementationExport
export function getWorkerImplementation(): ImplementationExport {
if (!implementation) {
implementation = selectWorkerImplementation()
}
return implementation
}
export function isWorkerRuntime() {
const isWindowContext = typeof globalThis !== 'undefined' && typeof Window !== 'undefined' && globalThis instanceof Window
return typeof globalThis !== 'undefined' && self['postMessage'] && !isWindowContext ? true : false
}