@vitest/web-worker 0.25.8 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -6
- package/dist/index.js +4 -3
- package/dist/pure.js +520 -80
- package/package.json +6 -2
- package/pure.d.ts +7 -2
package/README.md
CHANGED
@@ -2,7 +2,14 @@
|
|
2
2
|
|
3
3
|
> Web Worker support for Vitest testing. Doesn't require JSDom.
|
4
4
|
|
5
|
-
Simulates Web Worker, but in the same thread.
|
5
|
+
Simulates Web Worker, but in the same thread.
|
6
|
+
|
7
|
+
Supported:
|
8
|
+
|
9
|
+
- `new Worker(path)`
|
10
|
+
- `new SharedWorker(path)`
|
11
|
+
- `import MyWorker from './worker?worker'`
|
12
|
+
- `import MySharedWorker from './worker?sharedworker'`
|
6
13
|
|
7
14
|
## Installing
|
8
15
|
|
@@ -33,18 +40,36 @@ export default defineConfig({
|
|
33
40
|
})
|
34
41
|
```
|
35
42
|
|
43
|
+
You can also import `defineWebWorkers` from `@vitest/web-worker/pure` to define workers, whenever you need:
|
44
|
+
|
45
|
+
```js
|
46
|
+
import { defineWebWorkers } from '@vitest/web-worker/pure'
|
47
|
+
|
48
|
+
if (process.env.SUPPORT_WORKERS)
|
49
|
+
defineWebWorkers({ clone: 'none' })
|
50
|
+
```
|
51
|
+
|
52
|
+
It accepts options:
|
53
|
+
|
54
|
+
- `clone`: `'native' | 'ponyfill' | 'none'`. Defines how should `Worker` clone message, when transferring data. Applies only to `Worker` communication. `SharedWorker` uses `MessageChannel` from Node's `worker_threads` module, and is not configurable.
|
55
|
+
|
56
|
+
> **Note**
|
57
|
+
> Requires Node 17, if you want to use native `structuredClone`. Otherwise, it fallbacks to [polyfill](https://github.com/ungap/structured-clone), if not specified as `none`. You can also configure this option with `VITEST_WEB_WORKER_CLONE` environmental variable.
|
58
|
+
|
36
59
|
## Examples
|
37
60
|
|
38
61
|
```ts
|
39
62
|
// worker.ts
|
40
|
-
import '@vitest/web-worker'
|
41
|
-
import MyWorker from '../worker?worker'
|
42
|
-
|
43
63
|
self.onmessage = (e) => {
|
44
64
|
self.postMessage(`${e.data} world`)
|
45
65
|
}
|
66
|
+
```
|
46
67
|
|
68
|
+
```ts
|
47
69
|
// worker.test.ts
|
70
|
+
import '@vitest/web-worker'
|
71
|
+
import MyWorker from '../worker?worker'
|
72
|
+
|
48
73
|
let worker = new MyWorker()
|
49
74
|
// new Worker is also supported
|
50
75
|
worker = new Worker(new URL('../src/worker.ts', import.meta.url))
|
@@ -55,6 +80,10 @@ worker.onmessage = (e) => {
|
|
55
80
|
}
|
56
81
|
```
|
57
82
|
|
58
|
-
##
|
83
|
+
## Notes
|
59
84
|
|
60
|
-
-
|
85
|
+
- Worker does not support `onmessage = () => {}`. Please, use `self.onmessage = () => {}`.
|
86
|
+
- Shared worker does not support `onconnect = () => {}`. Please, use `self.onconnect = () => {}`.
|
87
|
+
- Transferring Buffer will not change its `byteLength`.
|
88
|
+
- You have access to shared global space as your tests.
|
89
|
+
- You can debug your worker, using `DEBUG=vitest:web-worker` environmental variable.
|
package/dist/index.js
CHANGED
package/dist/pure.js
CHANGED
@@ -1,32 +1,7 @@
|
|
1
1
|
import { VitestRunner } from 'vitest/node';
|
2
|
-
import
|
2
|
+
import createDebug from 'debug';
|
3
|
+
import { MessageChannel } from 'worker_threads';
|
3
4
|
|
4
|
-
function getWorkerState() {
|
5
|
-
return globalThis.__vitest_worker__;
|
6
|
-
}
|
7
|
-
class Bridge {
|
8
|
-
constructor() {
|
9
|
-
this.callbacks = {};
|
10
|
-
}
|
11
|
-
on(event, fn) {
|
12
|
-
var _a;
|
13
|
-
(_a = this.callbacks)[event] ?? (_a[event] = []);
|
14
|
-
this.callbacks[event].push(fn);
|
15
|
-
}
|
16
|
-
off(event, fn) {
|
17
|
-
if (this.callbacks[event])
|
18
|
-
this.callbacks[event] = this.callbacks[event].filter((f) => f !== fn);
|
19
|
-
}
|
20
|
-
removeEvents(event) {
|
21
|
-
this.callbacks[event] = [];
|
22
|
-
}
|
23
|
-
clear() {
|
24
|
-
this.callbacks = {};
|
25
|
-
}
|
26
|
-
emit(event, ...data) {
|
27
|
-
return (this.callbacks[event] || []).map((fn) => fn(...data));
|
28
|
-
}
|
29
|
-
}
|
30
5
|
class InlineWorkerRunner extends VitestRunner {
|
31
6
|
constructor(options, context) {
|
32
7
|
super(options);
|
@@ -34,19 +9,322 @@ class InlineWorkerRunner extends VitestRunner {
|
|
34
9
|
}
|
35
10
|
prepareContext(context) {
|
36
11
|
const ctx = super.prepareContext(context);
|
37
|
-
|
12
|
+
const importScripts = () => {
|
13
|
+
throw new Error("[vitest] `importScripts` is not supported in Vite workers. Please, consider using `import` instead.");
|
38
14
|
};
|
39
15
|
return Object.assign(ctx, this.context, {
|
40
|
-
importScripts
|
16
|
+
importScripts
|
17
|
+
});
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
const VOID = -1;
|
22
|
+
const PRIMITIVE = 0;
|
23
|
+
const ARRAY = 1;
|
24
|
+
const OBJECT = 2;
|
25
|
+
const DATE = 3;
|
26
|
+
const REGEXP = 4;
|
27
|
+
const MAP = 5;
|
28
|
+
const SET = 6;
|
29
|
+
const ERROR = 7;
|
30
|
+
const BIGINT = 8;
|
31
|
+
// export const SYMBOL = 9;
|
32
|
+
|
33
|
+
const env = typeof self === 'object' ? self : globalThis;
|
34
|
+
|
35
|
+
const deserializer = ($, _) => {
|
36
|
+
const as = (out, index) => {
|
37
|
+
$.set(index, out);
|
38
|
+
return out;
|
39
|
+
};
|
40
|
+
|
41
|
+
const unpair = index => {
|
42
|
+
if ($.has(index))
|
43
|
+
return $.get(index);
|
44
|
+
|
45
|
+
const [type, value] = _[index];
|
46
|
+
switch (type) {
|
47
|
+
case PRIMITIVE:
|
48
|
+
case VOID:
|
49
|
+
return as(value, index);
|
50
|
+
case ARRAY: {
|
51
|
+
const arr = as([], index);
|
52
|
+
for (const index of value)
|
53
|
+
arr.push(unpair(index));
|
54
|
+
return arr;
|
55
|
+
}
|
56
|
+
case OBJECT: {
|
57
|
+
const object = as({}, index);
|
58
|
+
for (const [key, index] of value)
|
59
|
+
object[unpair(key)] = unpair(index);
|
60
|
+
return object;
|
61
|
+
}
|
62
|
+
case DATE:
|
63
|
+
return as(new Date(value), index);
|
64
|
+
case REGEXP: {
|
65
|
+
const {source, flags} = value;
|
66
|
+
return as(new RegExp(source, flags), index);
|
67
|
+
}
|
68
|
+
case MAP: {
|
69
|
+
const map = as(new Map, index);
|
70
|
+
for (const [key, index] of value)
|
71
|
+
map.set(unpair(key), unpair(index));
|
72
|
+
return map;
|
41
73
|
}
|
74
|
+
case SET: {
|
75
|
+
const set = as(new Set, index);
|
76
|
+
for (const index of value)
|
77
|
+
set.add(unpair(index));
|
78
|
+
return set;
|
79
|
+
}
|
80
|
+
case ERROR: {
|
81
|
+
const {name, message} = value;
|
82
|
+
return as(new env[name](message), index);
|
83
|
+
}
|
84
|
+
case BIGINT:
|
85
|
+
return as(BigInt(value), index);
|
86
|
+
case 'BigInt':
|
87
|
+
return as(Object(BigInt(value)), index);
|
88
|
+
}
|
89
|
+
return as(new env[type](value), index);
|
90
|
+
};
|
91
|
+
|
92
|
+
return unpair;
|
93
|
+
};
|
94
|
+
|
95
|
+
/**
|
96
|
+
* @typedef {Array<string,any>} Record a type representation
|
97
|
+
*/
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Returns a deserialized value from a serialized array of Records.
|
101
|
+
* @param {Record[]} serialized a previously serialized value.
|
102
|
+
* @returns {any}
|
103
|
+
*/
|
104
|
+
const deserialize = serialized => deserializer(new Map, serialized)(0);
|
105
|
+
|
106
|
+
const EMPTY = '';
|
107
|
+
|
108
|
+
const {toString} = {};
|
109
|
+
const {keys} = Object;
|
110
|
+
|
111
|
+
const typeOf = value => {
|
112
|
+
const type = typeof value;
|
113
|
+
if (type !== 'object' || !value)
|
114
|
+
return [PRIMITIVE, type];
|
115
|
+
|
116
|
+
const asString = toString.call(value).slice(8, -1);
|
117
|
+
switch (asString) {
|
118
|
+
case 'Array':
|
119
|
+
return [ARRAY, EMPTY];
|
120
|
+
case 'Object':
|
121
|
+
return [OBJECT, EMPTY];
|
122
|
+
case 'Date':
|
123
|
+
return [DATE, EMPTY];
|
124
|
+
case 'RegExp':
|
125
|
+
return [REGEXP, EMPTY];
|
126
|
+
case 'Map':
|
127
|
+
return [MAP, EMPTY];
|
128
|
+
case 'Set':
|
129
|
+
return [SET, EMPTY];
|
130
|
+
}
|
131
|
+
|
132
|
+
if (asString.includes('Array'))
|
133
|
+
return [ARRAY, asString];
|
134
|
+
|
135
|
+
if (asString.includes('Error'))
|
136
|
+
return [ERROR, asString];
|
137
|
+
|
138
|
+
return [OBJECT, asString];
|
139
|
+
};
|
140
|
+
|
141
|
+
const shouldSkip = ([TYPE, type]) => (
|
142
|
+
TYPE === PRIMITIVE &&
|
143
|
+
(type === 'function' || type === 'symbol')
|
144
|
+
);
|
145
|
+
|
146
|
+
const serializer = (strict, json, $, _) => {
|
147
|
+
|
148
|
+
const as = (out, value) => {
|
149
|
+
const index = _.push(out) - 1;
|
150
|
+
$.set(value, index);
|
151
|
+
return index;
|
152
|
+
};
|
153
|
+
|
154
|
+
const pair = value => {
|
155
|
+
if ($.has(value))
|
156
|
+
return $.get(value);
|
157
|
+
|
158
|
+
let [TYPE, type] = typeOf(value);
|
159
|
+
switch (TYPE) {
|
160
|
+
case PRIMITIVE: {
|
161
|
+
let entry = value;
|
162
|
+
switch (type) {
|
163
|
+
case 'bigint':
|
164
|
+
TYPE = BIGINT;
|
165
|
+
entry = value.toString();
|
166
|
+
break;
|
167
|
+
case 'function':
|
168
|
+
case 'symbol':
|
169
|
+
if (strict)
|
170
|
+
throw new TypeError('unable to serialize ' + type);
|
171
|
+
entry = null;
|
172
|
+
break;
|
173
|
+
case 'undefined':
|
174
|
+
return as([VOID], value);
|
175
|
+
}
|
176
|
+
return as([TYPE, entry], value);
|
177
|
+
}
|
178
|
+
case ARRAY: {
|
179
|
+
if (type)
|
180
|
+
return as([type, [...value]], value);
|
181
|
+
|
182
|
+
const arr = [];
|
183
|
+
const index = as([TYPE, arr], value);
|
184
|
+
for (const entry of value)
|
185
|
+
arr.push(pair(entry));
|
186
|
+
return index;
|
187
|
+
}
|
188
|
+
case OBJECT: {
|
189
|
+
if (type) {
|
190
|
+
switch (type) {
|
191
|
+
case 'BigInt':
|
192
|
+
return as([type, value.toString()], value);
|
193
|
+
case 'Boolean':
|
194
|
+
case 'Number':
|
195
|
+
case 'String':
|
196
|
+
return as([type, value.valueOf()], value);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
if (json && ('toJSON' in value))
|
201
|
+
return pair(value.toJSON());
|
202
|
+
|
203
|
+
const entries = [];
|
204
|
+
const index = as([TYPE, entries], value);
|
205
|
+
for (const key of keys(value)) {
|
206
|
+
if (strict || !shouldSkip(typeOf(value[key])))
|
207
|
+
entries.push([pair(key), pair(value[key])]);
|
208
|
+
}
|
209
|
+
return index;
|
210
|
+
}
|
211
|
+
case DATE:
|
212
|
+
return as([TYPE, value.toISOString()], value);
|
213
|
+
case REGEXP: {
|
214
|
+
const {source, flags} = value;
|
215
|
+
return as([TYPE, {source, flags}], value);
|
216
|
+
}
|
217
|
+
case MAP: {
|
218
|
+
const entries = [];
|
219
|
+
const index = as([TYPE, entries], value);
|
220
|
+
for (const [key, entry] of value) {
|
221
|
+
if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry))))
|
222
|
+
entries.push([pair(key), pair(entry)]);
|
223
|
+
}
|
224
|
+
return index;
|
225
|
+
}
|
226
|
+
case SET: {
|
227
|
+
const entries = [];
|
228
|
+
const index = as([TYPE, entries], value);
|
229
|
+
for (const entry of value) {
|
230
|
+
if (strict || !shouldSkip(typeOf(entry)))
|
231
|
+
entries.push(pair(entry));
|
232
|
+
}
|
233
|
+
return index;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
const {message} = value;
|
238
|
+
return as([TYPE, {name: type, message}], value);
|
239
|
+
};
|
240
|
+
|
241
|
+
return pair;
|
242
|
+
};
|
243
|
+
|
244
|
+
/**
|
245
|
+
* @typedef {Array<string,any>} Record a type representation
|
246
|
+
*/
|
247
|
+
|
248
|
+
/**
|
249
|
+
* Returns an array of serialized Records.
|
250
|
+
* @param {any} value a serializable value.
|
251
|
+
* @param {{lossy?: boolean}?} options an object with a `lossy` property that,
|
252
|
+
* if `true`, will not throw errors on incompatible types, and behave more
|
253
|
+
* like JSON stringify would behave. Symbol and Function will be discarded.
|
254
|
+
* @returns {Record[]}
|
255
|
+
*/
|
256
|
+
const serialize = (value, {json, lossy} = {}) => {
|
257
|
+
const _ = [];
|
258
|
+
return serializer(!(json || lossy), !!json, new Map, _)(value), _;
|
259
|
+
};
|
260
|
+
|
261
|
+
/**
|
262
|
+
* @typedef {Array<string,any>} Record a type representation
|
263
|
+
*/
|
264
|
+
|
265
|
+
/**
|
266
|
+
* Returns an array of serialized Records.
|
267
|
+
* @param {any} any a serializable value.
|
268
|
+
* @param {{transfer: any[]}?} options an object with a transfoer property.
|
269
|
+
* This is currently not supported, all values are always cloned.
|
270
|
+
* @returns {Record[]}
|
271
|
+
*/
|
272
|
+
var ponyfillStructuredClone = typeof structuredClone === "function" ?
|
273
|
+
/* c8 ignore start */
|
274
|
+
(any, options) => (
|
275
|
+
options && ('json' in options || 'lossy' in options) ?
|
276
|
+
deserialize(serialize(any, options)) : structuredClone(any)
|
277
|
+
) :
|
278
|
+
(any, options) => deserialize(serialize(any, options));
|
279
|
+
|
280
|
+
const debug = createDebug("vitest:web-worker");
|
281
|
+
function getWorkerState() {
|
282
|
+
return globalThis.__vitest_worker__;
|
283
|
+
}
|
284
|
+
function assertGlobalExists(name) {
|
285
|
+
if (!(name in globalThis))
|
286
|
+
throw new Error(`[@vitest/web-worker] Cannot initiate a custom Web Worker. "${name}" is not supported in this environment. Please, consider using jsdom or happy-dom environment.`);
|
287
|
+
}
|
288
|
+
function createClonedMessageEvent(data, transferOrOptions, clone) {
|
289
|
+
const transfer = Array.isArray(transferOrOptions) ? transferOrOptions : transferOrOptions == null ? void 0 : transferOrOptions.transfer;
|
290
|
+
debug("clone worker message %o", data);
|
291
|
+
const origin = typeof location === "undefined" ? void 0 : location.origin;
|
292
|
+
if (typeof structuredClone === "function" && clone === "native") {
|
293
|
+
debug("create message event, using native structured clone");
|
294
|
+
return new MessageEvent("message", {
|
295
|
+
data: structuredClone(data, { transfer }),
|
296
|
+
origin
|
297
|
+
});
|
298
|
+
}
|
299
|
+
if (clone !== "none") {
|
300
|
+
debug("create message event, using polifylled structured clone");
|
301
|
+
(transfer == null ? void 0 : transfer.length) && console.warn(
|
302
|
+
'[@vitest/web-worker] `structuredClone` is not supported in this environment. Falling back to polyfill, your transferable options will be lost. Set `VITEST_WEB_WORKER_CLONE` environmental variable to "none", if you don\'t want to loose it,or update to Node 17+.'
|
303
|
+
);
|
304
|
+
return new MessageEvent("message", {
|
305
|
+
data: ponyfillStructuredClone(data, { lossy: true }),
|
306
|
+
origin
|
42
307
|
});
|
43
308
|
}
|
309
|
+
debug("create message event without cloning an object");
|
310
|
+
return new MessageEvent("message", {
|
311
|
+
data,
|
312
|
+
origin
|
313
|
+
});
|
44
314
|
}
|
45
|
-
function
|
46
|
-
|
47
|
-
return;
|
315
|
+
function createMessageEvent(data, transferOrOptions, clone) {
|
316
|
+
try {
|
317
|
+
return createClonedMessageEvent(data, transferOrOptions, clone);
|
318
|
+
} catch (error) {
|
319
|
+
debug('failed to clone message, dispatch "messageerror" event: %o', error);
|
320
|
+
return new MessageEvent("messageerror", {
|
321
|
+
data: error
|
322
|
+
});
|
323
|
+
}
|
324
|
+
}
|
325
|
+
function getRunnerOptions() {
|
48
326
|
const { config, rpc, mockMap, moduleCache } = getWorkerState();
|
49
|
-
|
327
|
+
return {
|
50
328
|
fetchModule(id) {
|
51
329
|
return rpc.fetch(id);
|
52
330
|
},
|
@@ -59,24 +337,41 @@ function defineWebWorker() {
|
|
59
337
|
root: config.root,
|
60
338
|
base: config.base
|
61
339
|
};
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
340
|
+
}
|
341
|
+
|
342
|
+
function createWorkerConstructor(options) {
|
343
|
+
var _a;
|
344
|
+
const runnerOptions = getRunnerOptions();
|
345
|
+
const cloneType = () => (options == null ? void 0 : options.clone) ?? process.env.VITEST_WEB_WORKER_CLONE ?? "native";
|
346
|
+
return _a = class extends EventTarget {
|
347
|
+
constructor(url, options2) {
|
348
|
+
super();
|
349
|
+
this._vw_workerTarget = new EventTarget();
|
350
|
+
this._vw_insideListeners = /* @__PURE__ */ new Map();
|
351
|
+
this._vw_outsideListeners = /* @__PURE__ */ new Map();
|
352
|
+
this._vw_messageQueue = [];
|
67
353
|
this.onmessage = null;
|
68
354
|
this.onmessageerror = null;
|
69
355
|
this.onerror = null;
|
70
356
|
const context = {
|
71
357
|
onmessage: null,
|
358
|
+
name: options2 == null ? void 0 : options2.name,
|
359
|
+
close: () => this.terminate(),
|
72
360
|
dispatchEvent: (event) => {
|
73
|
-
this.
|
74
|
-
|
361
|
+
return this._vw_workerTarget.dispatchEvent(event);
|
362
|
+
},
|
363
|
+
addEventListener: (...args) => {
|
364
|
+
if (args[1])
|
365
|
+
this._vw_insideListeners.set(args[0], args[1]);
|
366
|
+
return this._vw_workerTarget.addEventListener(...args);
|
75
367
|
},
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
368
|
+
removeEventListener: this._vw_workerTarget.removeEventListener,
|
369
|
+
postMessage: (...args) => {
|
370
|
+
if (!args.length)
|
371
|
+
throw new SyntaxError('"postMessage" requires at least one argument.');
|
372
|
+
debug("posting message %o from the worker %s to the main thread", args[0], this._vw_name);
|
373
|
+
const event = createMessageEvent(args[0], args[1], cloneType());
|
374
|
+
this.dispatchEvent(event);
|
80
375
|
},
|
81
376
|
get self() {
|
82
377
|
return context;
|
@@ -85,51 +380,196 @@ function defineWebWorker() {
|
|
85
380
|
return context;
|
86
381
|
}
|
87
382
|
};
|
88
|
-
this.
|
89
|
-
var
|
90
|
-
(
|
383
|
+
this._vw_workerTarget.addEventListener("message", (e) => {
|
384
|
+
var _a2;
|
385
|
+
(_a2 = context.onmessage) == null ? void 0 : _a2.call(context, e);
|
386
|
+
});
|
387
|
+
this.addEventListener("message", (e) => {
|
388
|
+
var _a2;
|
389
|
+
(_a2 = this.onmessage) == null ? void 0 : _a2.call(this, e);
|
91
390
|
});
|
92
|
-
this.
|
93
|
-
var
|
94
|
-
(
|
391
|
+
this.addEventListener("messageerror", (e) => {
|
392
|
+
var _a2;
|
393
|
+
(_a2 = this.onmessageerror) == null ? void 0 : _a2.call(this, e);
|
95
394
|
});
|
96
|
-
const runner = new InlineWorkerRunner(
|
395
|
+
const runner = new InlineWorkerRunner(runnerOptions, context);
|
97
396
|
const id = (url instanceof URL ? url.toString() : url).replace(/^file:\/+/, "/");
|
98
|
-
|
99
|
-
runner.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
q
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
397
|
+
this._vw_name = id;
|
398
|
+
runner.resolveUrl(id).then(([, fsPath]) => {
|
399
|
+
this._vw_name = (options2 == null ? void 0 : options2.name) ?? fsPath;
|
400
|
+
debug("initialize worker %s", this._vw_name);
|
401
|
+
runner.executeFile(fsPath).then(() => {
|
402
|
+
runnerOptions.moduleCache.invalidateSubDepTree([fsPath, runner.mocker.getMockPath(fsPath)]);
|
403
|
+
const q = this._vw_messageQueue;
|
404
|
+
this._vw_messageQueue = null;
|
405
|
+
if (q)
|
406
|
+
q.forEach(([data, transfer]) => this.postMessage(data, transfer), this);
|
407
|
+
debug("worker %s successfully initialized", this._vw_name);
|
408
|
+
}).catch((e) => {
|
409
|
+
var _a2;
|
410
|
+
debug("worker %s failed to initialize: %o", this._vw_name, e);
|
411
|
+
const EventConstructor = globalThis.ErrorEvent || globalThis.Event;
|
412
|
+
const error = new EventConstructor("error", {
|
413
|
+
error: e,
|
414
|
+
message: e.message
|
415
|
+
});
|
416
|
+
this.dispatchEvent(error);
|
417
|
+
(_a2 = this.onerror) == null ? void 0 : _a2.call(this, error);
|
418
|
+
console.error(e);
|
419
|
+
});
|
110
420
|
});
|
111
421
|
}
|
112
|
-
|
113
|
-
|
114
|
-
|
422
|
+
addEventListener(type, callback, options2) {
|
423
|
+
if (callback)
|
424
|
+
this._vw_outsideListeners.set(type, callback);
|
425
|
+
return super.addEventListener(type, callback, options2);
|
115
426
|
}
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
this.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
427
|
+
postMessage(...args) {
|
428
|
+
if (!args.length)
|
429
|
+
throw new SyntaxError('"postMessage" requires at least one argument.');
|
430
|
+
const [data, transferOrOptions] = args;
|
431
|
+
if (this._vw_messageQueue != null) {
|
432
|
+
debug("worker %s is not yet initialized, queue message %s", this._vw_name, data);
|
433
|
+
this._vw_messageQueue.push([data, transferOrOptions]);
|
434
|
+
return;
|
435
|
+
}
|
436
|
+
debug("posting message %o from the main thread to the worker %s", data, this._vw_name);
|
437
|
+
const event = createMessageEvent(data, transferOrOptions, cloneType());
|
438
|
+
if (event.type === "messageerror")
|
439
|
+
this.dispatchEvent(event);
|
125
440
|
else
|
126
|
-
this.
|
441
|
+
this._vw_workerTarget.dispatchEvent(event);
|
127
442
|
}
|
128
443
|
terminate() {
|
129
|
-
this.
|
130
|
-
this.
|
444
|
+
debug("terminating worker %s", this._vw_name);
|
445
|
+
this._vw_outsideListeners.forEach((fn, type) => {
|
446
|
+
this.removeEventListener(type, fn);
|
447
|
+
});
|
448
|
+
this._vw_insideListeners.forEach((fn, type) => {
|
449
|
+
this._vw_workerTarget.removeEventListener(type, fn);
|
450
|
+
});
|
131
451
|
}
|
132
|
-
};
|
452
|
+
}, _a.__VITEST_WEB_WORKER__ = true, _a;
|
453
|
+
}
|
454
|
+
|
455
|
+
const convertNodePortToWebPort = (port) => {
|
456
|
+
if (!("addEventListener" in port)) {
|
457
|
+
Object.defineProperty(port, "addEventListener", {
|
458
|
+
value(...args) {
|
459
|
+
return this.addListener(...args);
|
460
|
+
},
|
461
|
+
configurable: true,
|
462
|
+
enumerable: true
|
463
|
+
});
|
464
|
+
}
|
465
|
+
if (!("removeEventListener" in port)) {
|
466
|
+
Object.defineProperty(port, "removeEventListener", {
|
467
|
+
value(...args) {
|
468
|
+
return this.removeListener(...args);
|
469
|
+
},
|
470
|
+
configurable: true,
|
471
|
+
enumerable: true
|
472
|
+
});
|
473
|
+
}
|
474
|
+
if (!("dispatchEvent" in port)) {
|
475
|
+
const emit = port.emit.bind(port);
|
476
|
+
Object.defineProperty(port, "emit", {
|
477
|
+
value(event) {
|
478
|
+
var _a, _b;
|
479
|
+
if (event.name === "message")
|
480
|
+
(_a = port.onmessage) == null ? void 0 : _a.call(port, event);
|
481
|
+
if (event.name === "messageerror")
|
482
|
+
(_b = port.onmessageerror) == null ? void 0 : _b.call(port, event);
|
483
|
+
return emit(event);
|
484
|
+
},
|
485
|
+
configurable: true,
|
486
|
+
enumerable: true
|
487
|
+
});
|
488
|
+
Object.defineProperty(port, "dispatchEvent", {
|
489
|
+
value(event) {
|
490
|
+
return this.emit(event);
|
491
|
+
},
|
492
|
+
configurable: true,
|
493
|
+
enumerable: true
|
494
|
+
});
|
495
|
+
}
|
496
|
+
return port;
|
497
|
+
};
|
498
|
+
function createSharedWorkerConstructor() {
|
499
|
+
var _a;
|
500
|
+
const runnerOptions = getRunnerOptions();
|
501
|
+
return _a = class extends EventTarget {
|
502
|
+
constructor(url, options) {
|
503
|
+
super();
|
504
|
+
this._vw_workerTarget = new EventTarget();
|
505
|
+
this.onerror = null;
|
506
|
+
const name = typeof options === "string" ? options : options == null ? void 0 : options.name;
|
507
|
+
const context = {
|
508
|
+
onconnect: null,
|
509
|
+
name,
|
510
|
+
close: () => this.port.close(),
|
511
|
+
dispatchEvent: (event) => {
|
512
|
+
return this._vw_workerTarget.dispatchEvent(event);
|
513
|
+
},
|
514
|
+
addEventListener: (...args) => {
|
515
|
+
return this._vw_workerTarget.addEventListener(...args);
|
516
|
+
},
|
517
|
+
removeEventListener: this._vw_workerTarget.removeEventListener,
|
518
|
+
get self() {
|
519
|
+
return context;
|
520
|
+
},
|
521
|
+
get global() {
|
522
|
+
return context;
|
523
|
+
}
|
524
|
+
};
|
525
|
+
const channel = new MessageChannel();
|
526
|
+
this.port = convertNodePortToWebPort(channel.port1);
|
527
|
+
this._vw_workerPort = convertNodePortToWebPort(channel.port2);
|
528
|
+
this._vw_workerTarget.addEventListener("connect", (e) => {
|
529
|
+
var _a2;
|
530
|
+
(_a2 = context.onconnect) == null ? void 0 : _a2.call(context, e);
|
531
|
+
});
|
532
|
+
const runner = new InlineWorkerRunner(runnerOptions, context);
|
533
|
+
const id = (url instanceof URL ? url.toString() : url).replace(/^file:\/+/, "/");
|
534
|
+
this._vw_name = id;
|
535
|
+
runner.resolveUrl(id).then(([, fsPath]) => {
|
536
|
+
this._vw_name = name ?? fsPath;
|
537
|
+
debug("initialize shared worker %s", this._vw_name);
|
538
|
+
runner.executeFile(fsPath).then(() => {
|
539
|
+
runnerOptions.moduleCache.invalidateSubDepTree([fsPath, runner.mocker.getMockPath(fsPath)]);
|
540
|
+
this._vw_workerTarget.dispatchEvent(
|
541
|
+
new MessageEvent("connect", {
|
542
|
+
ports: [this._vw_workerPort]
|
543
|
+
})
|
544
|
+
);
|
545
|
+
debug("shared worker %s successfully initialized", this._vw_name);
|
546
|
+
}).catch((e) => {
|
547
|
+
var _a2;
|
548
|
+
debug("shared worker %s failed to initialize: %o", this._vw_name, e);
|
549
|
+
const EventConstructor = globalThis.ErrorEvent || globalThis.Event;
|
550
|
+
const error = new EventConstructor("error", {
|
551
|
+
error: e,
|
552
|
+
message: e.message
|
553
|
+
});
|
554
|
+
this.dispatchEvent(error);
|
555
|
+
(_a2 = this.onerror) == null ? void 0 : _a2.call(this, error);
|
556
|
+
console.error(e);
|
557
|
+
});
|
558
|
+
});
|
559
|
+
}
|
560
|
+
}, _a.__VITEST_WEB_WORKER__ = true, _a;
|
561
|
+
}
|
562
|
+
|
563
|
+
function defineWebWorkers(options) {
|
564
|
+
if (typeof Worker === "undefined" || !("__VITEST_WEB_WORKER__" in globalThis.Worker)) {
|
565
|
+
assertGlobalExists("EventTarget");
|
566
|
+
assertGlobalExists("MessageEvent");
|
567
|
+
globalThis.Worker = createWorkerConstructor(options);
|
568
|
+
}
|
569
|
+
if (typeof SharedWorker === "undefined" || !("__VITEST_WEB_WORKER__" in globalThis.SharedWorker)) {
|
570
|
+
assertGlobalExists("EventTarget");
|
571
|
+
globalThis.SharedWorker = createSharedWorkerConstructor();
|
572
|
+
}
|
133
573
|
}
|
134
574
|
|
135
|
-
export {
|
575
|
+
export { defineWebWorkers };
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitest/web-worker",
|
3
3
|
"type": "module",
|
4
|
-
"version": "0.
|
4
|
+
"version": "0.26.0",
|
5
5
|
"description": "Web Worker support for testing in Vitest",
|
6
6
|
"repository": {
|
7
7
|
"type": "git",
|
@@ -31,9 +31,13 @@
|
|
31
31
|
"vitest": "*"
|
32
32
|
},
|
33
33
|
"dependencies": {
|
34
|
-
"
|
34
|
+
"debug": "^4.3.4",
|
35
|
+
"vite-node": "0.26.0"
|
35
36
|
},
|
36
37
|
"devDependencies": {
|
38
|
+
"@types/debug": "^4.1.7",
|
39
|
+
"@types/ungap__structured-clone": "^0.3.0",
|
40
|
+
"@ungap/structured-clone": "^1.0.1",
|
37
41
|
"rollup": "^2.79.1"
|
38
42
|
},
|
39
43
|
"scripts": {
|
package/pure.d.ts
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
-
|
1
|
+
type CloneOption = 'native' | 'ponyfill' | 'none';
|
2
|
+
interface DefineWorkerOptions {
|
3
|
+
clone: CloneOption;
|
4
|
+
}
|
2
5
|
|
3
|
-
|
6
|
+
declare function defineWebWorkers(options?: DefineWorkerOptions): void;
|
7
|
+
|
8
|
+
export { defineWebWorkers };
|