Skip to content

Commit 12bb567

Browse files
authoredJul 23, 2024··
fix(web-worker): expose globals on self (#6170)
1 parent 57d23ce commit 12bb567

File tree

8 files changed

+129
-54
lines changed

8 files changed

+129
-54
lines changed
 

‎docs/guide/mocking.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -759,9 +759,9 @@ it('the value is restored before running an other test', () => {
759759

760760
```ts
761761
// vitest.config.ts
762-
export default {
762+
export default defineConfig({
763763
test: {
764764
unstubAllEnvs: true,
765765
}
766-
}
766+
})
767767
```

‎packages/web-worker/src/shared-worker.ts

+35-23
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,10 @@ import {
22
MessageChannel,
33
type MessagePort as NodeMessagePort,
44
} from 'node:worker_threads'
5-
import type { InlineWorkerContext, Procedure } from './types'
5+
import type { Procedure } from './types'
66
import { InlineWorkerRunner } from './runner'
77
import { debug, getFileIdFromUrl, getRunnerOptions } from './utils'
88

9-
interface SharedInlineWorkerContext
10-
extends Omit<
11-
InlineWorkerContext,
12-
'onmessage' | 'postMessage' | 'self' | 'global'
13-
> {
14-
onconnect: Procedure | null
15-
self: SharedInlineWorkerContext
16-
global: SharedInlineWorkerContext
17-
}
18-
199
function convertNodePortToWebPort(port: NodeMessagePort): MessagePort {
2010
if (!('addEventListener' in port)) {
2111
Object.defineProperty(port, 'addEventListener', {
@@ -79,33 +69,55 @@ export function createSharedWorkerConstructor(): typeof SharedWorker {
7969
super()
8070

8171
const name = typeof options === 'string' ? options : options?.name
82-
83-
// should be equal to SharedWorkerGlobalScope
84-
const context: SharedInlineWorkerContext = {
85-
onconnect: null,
86-
name,
72+
let selfProxy: typeof globalThis
73+
74+
const context = {
75+
onmessage: null,
76+
onmessageerror: null,
77+
onerror: null,
78+
onlanguagechange: null,
79+
onoffline: null,
80+
ononline: null,
81+
onrejectionhandled: null,
82+
onrtctransform: null,
83+
onunhandledrejection: null,
84+
origin: typeof location !== 'undefined' ? location.origin : 'http://localhost:3000',
85+
importScripts: () => {
86+
throw new Error(
87+
'[vitest] `importScripts` is not supported in Vite workers. Please, consider using `import` instead.',
88+
)
89+
},
90+
crossOriginIsolated: false,
91+
onconnect: null as ((e: MessageEvent) => void) | null,
92+
name: name || '',
8793
close: () => this.port.close(),
8894
dispatchEvent: (event: Event) => {
8995
return this._vw_workerTarget.dispatchEvent(event)
9096
},
91-
addEventListener: (...args) => {
92-
return this._vw_workerTarget.addEventListener(...args)
97+
addEventListener: (...args: any[]) => {
98+
return this._vw_workerTarget.addEventListener(...args as [any, any])
9399
},
94100
removeEventListener: this._vw_workerTarget.removeEventListener,
95101
get self() {
96-
return context
97-
},
98-
get global() {
99-
return context
102+
return selfProxy
100103
},
101104
}
102105

106+
selfProxy = new Proxy(context, {
107+
get(target, prop, receiver) {
108+
if (Reflect.has(target, prop)) {
109+
return Reflect.get(target, prop, receiver)
110+
}
111+
return Reflect.get(globalThis, prop, receiver)
112+
},
113+
}) as any
114+
103115
const channel = new MessageChannel()
104116
this.port = convertNodePortToWebPort(channel.port1)
105117
this._vw_workerPort = convertNodePortToWebPort(channel.port2)
106118

107119
this._vw_workerTarget.addEventListener('connect', (e) => {
108-
context.onconnect?.(e)
120+
context.onconnect?.(e as MessageEvent)
109121
})
110122

111123
const runner = new InlineWorkerRunner(runnerOptions, context)

‎packages/web-worker/src/types.ts

-16
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,3 @@ export type CloneOption = 'native' | 'ponyfill' | 'none'
44
export interface DefineWorkerOptions {
55
clone: CloneOption
66
}
7-
8-
export interface InlineWorkerContext {
9-
onmessage: Procedure | null
10-
name?: string
11-
close: () => void
12-
dispatchEvent: (e: Event) => void
13-
addEventListener: (e: string, fn: Procedure) => void
14-
removeEventListener: (e: string, fn: Procedure) => void
15-
postMessage: (
16-
data: any,
17-
transfer?: Transferable[] | StructuredSerializeOptions
18-
) => void
19-
self: InlineWorkerContext
20-
global: InlineWorkerContext
21-
importScripts?: any
22-
}

‎packages/web-worker/src/worker.ts

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type {
22
CloneOption,
33
DefineWorkerOptions,
4-
InlineWorkerContext,
54
Procedure,
65
} from './types'
76
import { InlineWorkerRunner } from './runner'
@@ -45,22 +44,39 @@ export function createWorkerConstructor(
4544
constructor(url: URL | string, options?: WorkerOptions) {
4645
super()
4746

48-
// should be equal to DedicatedWorkerGlobalScope
49-
const context: InlineWorkerContext = {
50-
onmessage: null,
51-
name: options?.name,
47+
let selfProxy: typeof globalThis
48+
49+
// should be in sync with DedicatedWorkerGlobalScope, but without globalThis
50+
const context = {
51+
onmessage: null as null | ((e: MessageEvent) => void),
52+
onmessageerror: null,
53+
onerror: null,
54+
onlanguagechange: null,
55+
onoffline: null,
56+
ononline: null,
57+
onrejectionhandled: null,
58+
onrtctransform: null,
59+
onunhandledrejection: null,
60+
origin: typeof location !== 'undefined' ? location.origin : 'http://localhost:3000',
61+
importScripts: () => {
62+
throw new Error(
63+
'[vitest] `importScripts` is not supported in Vite workers. Please, consider using `import` instead.',
64+
)
65+
},
66+
crossOriginIsolated: false,
67+
name: options?.name || '',
5268
close: () => this.terminate(),
5369
dispatchEvent: (event: Event) => {
5470
return this._vw_workerTarget.dispatchEvent(event)
5571
},
56-
addEventListener: (...args) => {
72+
addEventListener: (...args: any[]) => {
5773
if (args[1]) {
5874
this._vw_insideListeners.set(args[0], args[1])
5975
}
60-
return this._vw_workerTarget.addEventListener(...args)
76+
return this._vw_workerTarget.addEventListener(...args as [any, any])
6177
},
6278
removeEventListener: this._vw_workerTarget.removeEventListener,
63-
postMessage: (...args) => {
79+
postMessage: (...args: any[]) => {
6480
if (!args.length) {
6581
throw new SyntaxError(
6682
'"postMessage" requires at least one argument.',
@@ -76,15 +92,21 @@ export function createWorkerConstructor(
7692
this.dispatchEvent(event)
7793
},
7894
get self() {
79-
return context
80-
},
81-
get global() {
82-
return context
95+
return selfProxy
8396
},
8497
}
8598

99+
selfProxy = new Proxy(context, {
100+
get(target, prop, receiver) {
101+
if (Reflect.has(target, prop)) {
102+
return Reflect.get(target, prop, receiver)
103+
}
104+
return globalThis[prop as 'crypto']
105+
},
106+
}) as any
107+
86108
this._vw_workerTarget.addEventListener('message', (e) => {
87-
context.onmessage?.(e)
109+
context.onmessage?.(e as MessageEvent)
88110
})
89111

90112
this.addEventListener('message', (e) => {

‎packages/web-worker/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"extends": "../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"lib": ["ESNext", "WebWorker"]
5+
},
36
"exclude": ["./dist"]
47
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
self.onmessage = () => {
2+
self.postMessage({
3+
crypto: !!self.crypto,
4+
caches: !!self.caches,
5+
location: !!self.location,
6+
origin: self.origin,
7+
})
8+
}

‎test/core/test/web-worker-jsdom.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import '@vitest/web-worker'
44

55
import { expect, it } from 'vitest'
6+
import GlobalsWorker from '../src/web-worker/worker-globals?worker'
67

78
it('worker with invalid url throws an error', async () => {
89
const url = import.meta.url
@@ -35,3 +36,25 @@ it('throws an error on invalid path', async () => {
3536
}
3637
expect(event.error.message).toContain('Failed to load')
3738
})
39+
40+
it('returns globals on self correctly', async () => {
41+
const worker = new GlobalsWorker()
42+
await new Promise<void>((resolve, reject) => {
43+
worker.onmessage = (e) => {
44+
try {
45+
expect(e.data).toEqual({
46+
crypto: !!globalThis.crypto,
47+
location: !!globalThis.location,
48+
caches: !!globalThis.caches,
49+
origin: 'http://localhost:3000',
50+
})
51+
resolve()
52+
}
53+
catch (err) {
54+
reject(err)
55+
}
56+
}
57+
worker.onerror = reject
58+
worker.postMessage(null)
59+
})
60+
})

‎test/core/test/web-worker-node.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import MyObjectWorker from '../src/web-worker/objectWorker?worker'
1010
import MyEventListenerWorker from '../src/web-worker/eventListenerWorker?worker'
1111
import MySelfWorker from '../src/web-worker/selfWorker?worker'
1212
import MySharedWorker from '../src/web-worker/sharedWorker?sharedworker'
13+
import GlobalsWorker from '../src/web-worker/worker-globals?worker'
1314

1415
const major = Number(version.split('.')[0].slice(1))
1516

@@ -269,3 +270,25 @@ it('doesn\'t trigger events, if closed', async () => {
269270
setTimeout(resolve, 100)
270271
})
271272
})
273+
274+
it('returns globals on self correctly', async () => {
275+
const worker = new GlobalsWorker()
276+
await new Promise<void>((resolve, reject) => {
277+
worker.onmessage = (e) => {
278+
try {
279+
expect(e.data).toEqual({
280+
crypto: !!globalThis.crypto,
281+
location: !!globalThis.location,
282+
caches: !!globalThis.caches,
283+
origin: 'http://localhost:3000',
284+
})
285+
resolve()
286+
}
287+
catch (err) {
288+
reject(err)
289+
}
290+
}
291+
worker.onerror = reject
292+
worker.postMessage(null)
293+
})
294+
})

0 commit comments

Comments
 (0)
Please sign in to comment.