@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 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. Supports both `new Worker(url)` and `import from './worker?worker`.
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
- ## Notice
83
+ ## Notes
59
84
 
60
- - Does not support `onmessage = () => {}`. Please, use `self.onmessage = () => {}`.
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
@@ -1,5 +1,6 @@
1
- import { defineWebWorker } from './pure.js';
1
+ import { defineWebWorkers } from './pure.js';
2
2
  import 'vitest/node';
3
- import 'vite-node/utils';
3
+ import 'debug';
4
+ import 'worker_threads';
4
5
 
5
- defineWebWorker();
6
+ defineWebWorkers();
package/dist/pure.js CHANGED
@@ -1,32 +1,7 @@
1
1
  import { VitestRunner } from 'vitest/node';
2
- import { toFilePath } from 'vite-node/utils';
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
- this.context.self.importScripts = () => {
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 defineWebWorker() {
46
- if ("Worker" in globalThis)
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
- const options = {
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
- globalThis.Worker = class Worker {
63
- constructor(url) {
64
- this.inside = new Bridge();
65
- this.outside = new Bridge();
66
- this.messageQueue = [];
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.inside.emit(event.type, event);
74
- return true;
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
- addEventListener: this.inside.on.bind(this.inside),
77
- removeEventListener: this.inside.off.bind(this.inside),
78
- postMessage: (data) => {
79
- this.outside.emit("message", { data });
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.inside.on("message", (e) => {
89
- var _a;
90
- (_a = context.onmessage) == null ? void 0 : _a.call(context, e);
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.outside.on("message", (e) => {
93
- var _a;
94
- (_a = this.onmessage) == null ? void 0 : _a.call(this, e);
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(options, context);
395
+ const runner = new InlineWorkerRunner(runnerOptions, context);
97
396
  const id = (url instanceof URL ? url.toString() : url).replace(/^file:\/+/, "/");
98
- const fsPath = toFilePath(id, config.root);
99
- runner.executeFile(fsPath).then(() => {
100
- moduleCache.invalidateSubDepTree([fsPath, `mock:${fsPath}`]);
101
- const q = this.messageQueue;
102
- this.messageQueue = null;
103
- if (q)
104
- q.forEach(this.postMessage, this);
105
- }).catch((e) => {
106
- var _a;
107
- this.outside.emit("error", e);
108
- (_a = this.onerror) == null ? void 0 : _a.call(this, e);
109
- console.error(e);
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
- dispatchEvent(event) {
113
- this.outside.emit(event.type, event);
114
- return true;
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
- addEventListener(event, fn) {
117
- this.outside.on(event, fn);
118
- }
119
- removeEventListener(event, fn) {
120
- this.outside.off(event, fn);
121
- }
122
- postMessage(data) {
123
- if (this.messageQueue != null)
124
- this.messageQueue.push(data);
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.inside.emit("message", { data });
441
+ this._vw_workerTarget.dispatchEvent(event);
127
442
  }
128
443
  terminate() {
129
- this.outside.clear();
130
- this.inside.clear();
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 { defineWebWorker };
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.25.8",
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
- "vite-node": "0.25.8"
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
- declare function defineWebWorker(): void;
1
+ type CloneOption = 'native' | 'ponyfill' | 'none';
2
+ interface DefineWorkerOptions {
3
+ clone: CloneOption;
4
+ }
2
5
 
3
- export { defineWebWorker };
6
+ declare function defineWebWorkers(options?: DefineWorkerOptions): void;
7
+
8
+ export { defineWebWorkers };