Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
5.8 kB · 168 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169import type { CloneOption, DefineWorkerOptions, Procedure } from './types'import { startWebWorkerModuleRunner } from './runner'import { createMessageEvent, debug, getFileIdFromUrl } from './utils'
export function createWorkerConstructor(options?: DefineWorkerOptions): typeof Worker { const cloneType = () => (options?.clone ?? process.env.VITEST_WEB_WORKER_CLONE ?? 'native') as CloneOption
return class Worker extends EventTarget { static __VITEST_WEB_WORKER__ = true
private _vw_workerTarget = new EventTarget() private _vw_insideListeners = new Map<string, EventListenerOrEventListenerObject>()
private _vw_outsideListeners = new Map<string, EventListenerOrEventListenerObject>()
private _vw_name: string private _vw_messageQueue: any[] | null = []
public onmessage: null | Procedure = null public onmessageerror: null | Procedure = null public onerror: null | Procedure = null
constructor(url: URL | string, options?: WorkerOptions) { super()
let selfProxy: typeof globalThis
// should be in sync with DedicatedWorkerGlobalScope, but without globalThis const context = { onmessage: null as null | ((e: MessageEvent) => void), onmessageerror: null, onerror: null, onlanguagechange: null, onoffline: null, ononline: null, onrejectionhandled: null, onrtctransform: null, onunhandledrejection: null, origin: typeof location !== 'undefined' ? location.origin : 'http://localhost:3000', crossOriginIsolated: false, name: options?.name || '', close: () => this.terminate(), dispatchEvent: (event: Event) => { return this._vw_workerTarget.dispatchEvent(event) }, addEventListener: (...args: any[]) => { if (args[1]) { this._vw_insideListeners.set(args[0], args[1]) } return this._vw_workerTarget.addEventListener(...(args as [any, any])) }, removeEventListener: (...args: any[]) => { return this._vw_workerTarget.removeEventListener(...(args as [any, any])) }, postMessage: (...args: any[]) => { if (!args.length) { throw new SyntaxError('"postMessage" requires at least one argument.') }
debug('posting message %o from the worker %s to the main thread', args[0], this._vw_name) const event = createMessageEvent(args[0], args[1], cloneType()) this.dispatchEvent(event) }, get self() { return selfProxy }, }
selfProxy = new Proxy(context, { get(target, prop, receiver) { if (Reflect.has(target, prop)) { return Reflect.get(target, prop, receiver) } return globalThis[prop as 'crypto'] }, }) as any
this._vw_workerTarget.addEventListener('message', (e) => { context.onmessage?.(e as MessageEvent) })
this.addEventListener('message', (e) => { this.onmessage?.(e) })
this.addEventListener('messageerror', (e) => { this.onmessageerror?.(e) })
const fileId = getFileIdFromUrl(url)
this._vw_name = fileId
const runner = startWebWorkerModuleRunner(context) runner.mocker .resolveId(fileId) .then(({ url, id: resolvedId }) => { this._vw_name = options?.name ?? url debug('initialize worker %s', this._vw_name)
return runner.import(url).then(() => { runner._invalidateSubTreeById([resolvedId, runner.mocker.getMockPath(resolvedId)]) const q = this._vw_messageQueue this._vw_messageQueue = null if (q) { q.forEach(([data, transfer]) => this.postMessage(data, transfer), this) } debug('worker %s successfully initialized', this._vw_name) }) }) .catch((e) => { debug('worker %s failed to initialize: %o', this._vw_name, e) const EventConstructor = globalThis.ErrorEvent || globalThis.Event const error = new EventConstructor('error', { error: e, message: e.message, }) this.dispatchEvent(error) this.onerror?.(error) console.error(e) }) }
addEventListener( type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions, ): void { if (callback) { this._vw_outsideListeners.set(type, callback) } return super.addEventListener(type, callback, options) }
postMessage(...args: [any, StructuredSerializeOptions | Transferable[] | undefined]): void { if (!args.length) { throw new SyntaxError('"postMessage" requires at least one argument.') }
const [data, transferOrOptions] = args if (this._vw_messageQueue != null) { debug('worker %s is not yet initialized, queue message %s', this._vw_name, data) this._vw_messageQueue.push([data, transferOrOptions]) return }
debug('posting message %o from the main thread to the worker %s', data, this._vw_name)
const event = createMessageEvent(data, transferOrOptions, cloneType()) if (event.type === 'messageerror') { this.dispatchEvent(event) } else { this._vw_workerTarget.dispatchEvent(event) } }
terminate() { debug('terminating worker %s', this._vw_name) this._vw_outsideListeners.forEach((fn, type) => { this.removeEventListener(type, fn) }) this._vw_insideListeners.forEach((fn, type) => { this._vw_workerTarget.removeEventListener(type, fn) }) } }}