/
shared-worker.ts
136 lines (118 loc) · 4.2 KB
/
shared-worker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { MessageChannel, type MessagePort as NodeMessagePort } from 'worker_threads'
import type { InlineWorkerContext, Procedure } from './types'
import { InlineWorkerRunner } from './runner'
import { debug, getRunnerOptions } from './utils'
interface SharedInlineWorkerContext extends Omit<InlineWorkerContext, 'onmessage' | 'postMessage' | 'self' | 'global'> {
onconnect: Procedure | null
self: SharedInlineWorkerContext
global: SharedInlineWorkerContext
}
const convertNodePortToWebPort = (port: NodeMessagePort): MessagePort => {
if (!('addEventListener' in port)) {
Object.defineProperty(port, 'addEventListener', {
value(...args: any[]) {
return this.addListener(...args)
},
configurable: true,
enumerable: true,
})
}
if (!('removeEventListener' in port)) {
Object.defineProperty(port, 'removeEventListener', {
value(...args: any[]) {
return this.removeListener(...args)
},
configurable: true,
enumerable: true,
})
}
if (!('dispatchEvent' in port)) {
const emit = port.emit.bind(port)
Object.defineProperty(port, 'emit', {
value(event: any) {
if (event.name === 'message')
(port as any).onmessage?.(event)
if (event.name === 'messageerror')
(port as any).onmessageerror?.(event)
return emit(event)
},
configurable: true,
enumerable: true,
})
Object.defineProperty(port, 'dispatchEvent', {
value(event: any) {
return this.emit(event)
},
configurable: true,
enumerable: true,
})
}
return port as any as MessagePort
}
export function createSharedWorkerConstructor(): typeof SharedWorker {
const runnerOptions = getRunnerOptions()
return class SharedWorker extends EventTarget {
static __VITEST_WEB_WORKER__ = true
private _vw_workerTarget = new EventTarget()
private _vw_name: string
private _vw_workerPort: MessagePort
public onerror: null | Procedure = null
public port: MessagePort
constructor(url: URL | string, options?: WorkerOptions | string) {
super()
const name = typeof options === 'string' ? options : options?.name
// should be equal to SharedWorkerGlobalScope
const context: SharedInlineWorkerContext = {
onconnect: null,
name,
close: () => this.port.close(),
dispatchEvent: (event: Event) => {
return this._vw_workerTarget.dispatchEvent(event)
},
addEventListener: (...args) => {
return this._vw_workerTarget.addEventListener(...args)
},
removeEventListener: this._vw_workerTarget.removeEventListener,
get self() {
return context
},
get global() {
return context
},
}
const channel = new MessageChannel()
this.port = convertNodePortToWebPort(channel.port1)
this._vw_workerPort = convertNodePortToWebPort(channel.port2)
this._vw_workerTarget.addEventListener('connect', (e) => {
context.onconnect?.(e)
})
const runner = new InlineWorkerRunner(runnerOptions, context)
const id = (url instanceof URL ? url.toString() : url).replace(/^file:\/+/, '/')
this._vw_name = id
runner.resolveUrl(id).then(([, fsPath]) => {
this._vw_name = name ?? fsPath
debug('initialize shared worker %s', this._vw_name)
runner.executeFile(fsPath).then(() => {
// worker should be new every time, invalidate its sub dependency
runnerOptions.moduleCache.invalidateSubDepTree([fsPath, `mock:${fsPath}`])
this._vw_workerTarget.dispatchEvent(
new MessageEvent('connect', {
ports: [this._vw_workerPort],
}),
)
debug('shared worker %s successfully initialized', this._vw_name)
}).catch((e) => {
debug('shared 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)
})
})
}
}
}