@parcel/workers 2.0.0-beta.3 → 2.0.0-dev.1510

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/src/Worker.js CHANGED
@@ -23,6 +23,7 @@ type WorkerOpts = {|
23
23
  forcedKillTime: number,
24
24
  backend: BackendType,
25
25
  shouldPatchConsole?: boolean,
26
+ shouldTrace?: boolean,
26
27
  sharedReferences: $ReadOnlyMap<SharedReference, mixed>,
27
28
  |};
28
29
 
@@ -31,7 +32,7 @@ export default class Worker extends EventEmitter {
31
32
  +options: WorkerOpts;
32
33
  worker: WorkerImpl;
33
34
  id: number = WORKER_ID++;
34
- sharedReferences: $ReadOnlyMap<SharedReference, mixed> = new Map();
35
+ sentSharedReferences: Set<SharedReference> = new Set();
35
36
 
36
37
  calls: Map<number, WorkerCall> = new Map();
37
38
  exitCode: ?number = null;
@@ -47,18 +48,25 @@ export default class Worker extends EventEmitter {
47
48
  }
48
49
 
49
50
  async fork(forkModule: FilePath) {
50
- let filteredArgs = process.execArgv.filter(
51
- v => !/^--(debug|inspect|max-old-space-size=)/.test(v),
52
- );
53
-
54
- for (let i = 0; i < filteredArgs.length; i++) {
55
- let arg = filteredArgs[i];
56
- if (
57
- (arg === '-r' || arg === '--require') &&
58
- filteredArgs[i + 1] === '@parcel/register'
59
- ) {
60
- filteredArgs.splice(i, 2);
61
- i--;
51
+ let filteredArgs = [];
52
+ if (process.execArgv) {
53
+ filteredArgs = process.execArgv.filter(
54
+ v =>
55
+ !/^--(debug|inspect|no-opt|max-old-space-size=|max-semi-space-size=|expose-gc)/.test(
56
+ v,
57
+ ),
58
+ );
59
+
60
+ for (let i = 0; i < filteredArgs.length; i++) {
61
+ let arg = filteredArgs[i];
62
+ let isArgWithParam =
63
+ ((arg === '-r' || arg === '--require') &&
64
+ filteredArgs[i + 1] === '@parcel/register') ||
65
+ arg === '--title';
66
+ if (isArgWithParam) {
67
+ filteredArgs.splice(i, 2);
68
+ i--;
69
+ }
62
70
  }
63
71
  }
64
72
 
@@ -67,7 +75,7 @@ export default class Worker extends EventEmitter {
67
75
  // arg parsing logic adapted from https://stackoverflow.com/a/46946420/2352201
68
76
  let opts = [''];
69
77
  let quote = false;
70
- for (let c of nullthrows(process.env.NODE_OPTIONS.match(/\\?.|^$/g))) {
78
+ for (let c of nullthrows(process.env.NODE_OPTIONS.match(/.|^$/g))) {
71
79
  if (c === '"') {
72
80
  quote = !quote;
73
81
  } else if (!quote && c === ' ') {
@@ -107,6 +115,7 @@ export default class Worker extends EventEmitter {
107
115
  forkModule,
108
116
  {
109
117
  shouldPatchConsole: !!this.options.shouldPatchConsole,
118
+ shouldTrace: !!this.options.shouldTrace,
110
119
  },
111
120
  ],
112
121
  retries: 0,
@@ -135,6 +144,7 @@ export default class Worker extends EventEmitter {
135
144
  }
136
145
 
137
146
  sendSharedReference(ref: SharedReference, value: mixed): Promise<any> {
147
+ this.sentSharedReferences.add(ref);
138
148
  return new Promise((resolve, reject) => {
139
149
  this.call({
140
150
  method: 'createSharedReference',
package/src/WorkerFarm.js CHANGED
@@ -11,6 +11,8 @@ import type {
11
11
  } from './types';
12
12
  import type {HandleFunction} from './Handle';
13
13
 
14
+ import * as coreWorker from './core-worker';
15
+ import * as bus from './bus';
14
16
  import invariant from 'assert';
15
17
  import nullthrows from 'nullthrows';
16
18
  import EventEmitter from 'events';
@@ -26,8 +28,7 @@ import cpuCount from './cpuCount';
26
28
  import Handle from './Handle';
27
29
  import {child} from './childState';
28
30
  import {detectBackend} from './backend';
29
- import Profiler from './Profiler';
30
- import Trace from './Trace';
31
+ import {SamplingProfiler, Trace} from '@parcel/profiler';
31
32
  import fs from 'fs';
32
33
  import logger from '@parcel/logger';
33
34
 
@@ -44,11 +45,13 @@ export type FarmOptions = {|
44
45
  workerPath?: FilePath,
45
46
  backend: BackendType,
46
47
  shouldPatchConsole?: boolean,
48
+ shouldTrace?: boolean,
47
49
  |};
48
50
 
49
- type WorkerModule = {|
51
+ type WorkerModule = {
50
52
  +[string]: (...args: Array<mixed>) => Promise<mixed>,
51
- |};
53
+ ...
54
+ };
52
55
 
53
56
  export type WorkerApi = {|
54
57
  callMaster(CallRequest, ?boolean): Promise<mixed>,
@@ -60,6 +63,8 @@ export type WorkerApi = {|
60
63
 
61
64
  export {Handle};
62
65
 
66
+ const DEFAULT_MAX_CONCURRENT_CALLS: number = 30;
67
+
63
68
  /**
64
69
  * workerPath should always be defined inside farmOptions
65
70
  */
@@ -68,20 +73,25 @@ export default class WorkerFarm extends EventEmitter {
68
73
  callQueue: Array<WorkerCall> = [];
69
74
  ending: boolean = false;
70
75
  localWorker: WorkerModule;
76
+ localWorkerInit: ?Promise<void>;
71
77
  options: FarmOptions;
72
78
  run: HandleFunction;
73
79
  warmWorkers: number = 0;
80
+ readyWorkers: number = 0;
74
81
  workers: Map<number, Worker> = new Map();
75
82
  handles: Map<number, Handle> = new Map();
76
83
  sharedReferences: Map<SharedReference, mixed> = new Map();
77
84
  sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
78
- profiler: ?Profiler;
85
+ serializedSharedReferences: Map<SharedReference, ?ArrayBuffer> = new Map();
86
+ profiler: ?SamplingProfiler;
79
87
 
80
88
  constructor(farmOptions: $Shape<FarmOptions> = {}) {
81
89
  super();
82
90
  this.options = {
83
91
  maxConcurrentWorkers: WorkerFarm.getNumWorkers(),
84
- maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(),
92
+ maxConcurrentCallsPerWorker: WorkerFarm.getConcurrentCallsPerWorker(
93
+ farmOptions.shouldTrace ? 1 : DEFAULT_MAX_CONCURRENT_CALLS,
94
+ ),
85
95
  forcedKillTime: 500,
86
96
  warmWorkers: false,
87
97
  useLocalWorker: true, // TODO: setting this to false makes some tests fail, figure out why
@@ -93,10 +103,40 @@ export default class WorkerFarm extends EventEmitter {
93
103
  throw new Error('Please provide a worker path!');
94
104
  }
95
105
 
96
- // $FlowFixMe this must be dynamic
97
- this.localWorker = require(this.options.workerPath);
106
+ // $FlowFixMe
107
+ if (process.browser) {
108
+ if (this.options.workerPath === '@parcel/core/src/worker.js') {
109
+ this.localWorker = coreWorker;
110
+ } else {
111
+ throw new Error(
112
+ 'No dynamic require possible: ' + this.options.workerPath,
113
+ );
114
+ }
115
+ } else {
116
+ // $FlowFixMe this must be dynamic
117
+ this.localWorker = require(this.options.workerPath);
118
+ }
119
+
120
+ this.localWorkerInit =
121
+ this.localWorker.childInit != null ? this.localWorker.childInit() : null;
122
+
98
123
  this.run = this.createHandle('run');
99
124
 
125
+ // Worker thread stdout is by default piped into the process stdout, if there are enough worker
126
+ // threads to exceed the default listener limit, then anything else piping into stdout will trigger
127
+ // the `MaxListenersExceededWarning`, so we should ensure the max listeners is at least equal to the
128
+ // number of workers + 1 for the main thread.
129
+ //
130
+ // Note this can't be fixed easily where other things pipe into stdout - even after starting > 10 worker
131
+ // threads `process.stdout.getMaxListeners()` will still return 10, however adding another pipe into `stdout`
132
+ // will give the warning with `<worker count + 1>` as the number of listeners.
133
+ process.stdout?.setMaxListeners(
134
+ Math.max(
135
+ process.stdout.getMaxListeners(),
136
+ WorkerFarm.getNumWorkers() + 1,
137
+ ),
138
+ );
139
+
100
140
  this.startMaxWorkers();
101
141
  }
102
142
 
@@ -172,21 +212,35 @@ export default class WorkerFarm extends EventEmitter {
172
212
  );
173
213
  }
174
214
 
175
- createHandle(method: string): HandleFunction {
176
- return (...args) => {
215
+ createHandle(method: string, useMainThread: boolean = false): HandleFunction {
216
+ if (!this.options.useLocalWorker) {
217
+ useMainThread = false;
218
+ }
219
+
220
+ return async (...args) => {
177
221
  // Child process workers are slow to start (~600ms).
178
222
  // While we're waiting, just run on the main thread.
179
223
  // This significantly speeds up startup time.
180
- if (this.shouldUseRemoteWorkers()) {
224
+ if (this.shouldUseRemoteWorkers() && !useMainThread) {
181
225
  return this.addCall(method, [...args, false]);
182
226
  } else {
183
227
  if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) {
184
228
  this.warmupWorker(method, args);
185
229
  }
186
230
 
187
- let processedArgs = restoreDeserializedObject(
188
- prepareForSerialization([...args, false]),
189
- );
231
+ let processedArgs;
232
+ if (!useMainThread) {
233
+ processedArgs = restoreDeserializedObject(
234
+ prepareForSerialization([...args, false]),
235
+ );
236
+ } else {
237
+ processedArgs = args;
238
+ }
239
+
240
+ if (this.localWorkerInit != null) {
241
+ await this.localWorkerInit;
242
+ this.localWorkerInit = null;
243
+ }
190
244
  return this.localWorker[method](this.workerApi, ...processedArgs);
191
245
  }
192
246
  };
@@ -206,6 +260,7 @@ export default class WorkerFarm extends EventEmitter {
206
260
  forcedKillTime: this.options.forcedKillTime,
207
261
  backend: this.options.backend,
208
262
  shouldPatchConsole: this.options.shouldPatchConsole,
263
+ shouldTrace: this.options.shouldTrace,
209
264
  sharedReferences: this.sharedReferences,
210
265
  });
211
266
 
@@ -213,7 +268,13 @@ export default class WorkerFarm extends EventEmitter {
213
268
 
214
269
  worker.on('request', data => this.processRequest(data, worker));
215
270
 
216
- worker.on('ready', () => this.processQueue());
271
+ worker.on('ready', () => {
272
+ this.readyWorkers++;
273
+ if (this.readyWorkers === this.options.maxConcurrentWorkers) {
274
+ this.emit('ready');
275
+ }
276
+ this.processQueue();
277
+ });
217
278
  worker.on('response', () => this.processQueue());
218
279
 
219
280
  worker.on('error', err => this.onError(err, worker));
@@ -265,11 +326,24 @@ export default class WorkerFarm extends EventEmitter {
265
326
  }
266
327
 
267
328
  if (worker.calls.size < this.options.maxConcurrentCallsPerWorker) {
268
- worker.call(this.callQueue.shift());
329
+ this.callWorker(worker, this.callQueue.shift());
269
330
  }
270
331
  }
271
332
  }
272
333
 
334
+ async callWorker(worker: Worker, call: WorkerCall): Promise<void> {
335
+ for (let ref of this.sharedReferences.keys()) {
336
+ if (!worker.sentSharedReferences.has(ref)) {
337
+ await worker.sendSharedReference(
338
+ ref,
339
+ this.getSerializedSharedReference(ref),
340
+ );
341
+ }
342
+ }
343
+
344
+ worker.call(call);
345
+ }
346
+
273
347
  async processRequest(
274
348
  data: {|
275
349
  location: FilePath,
@@ -281,8 +355,17 @@ export default class WorkerFarm extends EventEmitter {
281
355
  if (handleId != null) {
282
356
  mod = nullthrows(this.handles.get(handleId)?.fn);
283
357
  } else if (location) {
284
- // $FlowFixMe this must be dynamic
285
- mod = require(location);
358
+ // $FlowFixMe
359
+ if (process.browser) {
360
+ if (location === '@parcel/workers/src/bus.js') {
361
+ mod = (bus: any);
362
+ } else {
363
+ throw new Error('No dynamic require possible: ' + location);
364
+ }
365
+ } else {
366
+ // $FlowFixMe this must be dynamic
367
+ mod = require(location);
368
+ }
286
369
  } else {
287
370
  throw new Error('Unknown request');
288
371
  }
@@ -392,33 +475,31 @@ export default class WorkerFarm extends EventEmitter {
392
475
  return handle;
393
476
  }
394
477
 
395
- async createSharedReference(
478
+ createSharedReference(
396
479
  value: mixed,
397
- // An optional, pre-serialized representation of the value to be used
398
- // in its place.
399
- buffer?: Buffer,
400
- ): Promise<{|ref: SharedReference, dispose(): Promise<mixed>|}> {
480
+ isCacheable: boolean = true,
481
+ ): {|ref: SharedReference, dispose(): Promise<mixed>|} {
401
482
  let ref = referenceId++;
402
483
  this.sharedReferences.set(ref, value);
403
484
  this.sharedReferencesByValue.set(value, ref);
404
-
405
- let toSend = buffer ? buffer.buffer : value;
406
- let promises = [];
407
- for (let worker of this.workers.values()) {
408
- if (worker.ready) {
409
- promises.push(worker.sendSharedReference(ref, toSend));
410
- }
485
+ if (!isCacheable) {
486
+ this.serializedSharedReferences.set(ref, null);
411
487
  }
412
488
 
413
- await Promise.all(promises);
414
-
415
489
  return {
416
490
  ref,
417
491
  dispose: () => {
418
492
  this.sharedReferences.delete(ref);
419
493
  this.sharedReferencesByValue.delete(value);
494
+ this.serializedSharedReferences.delete(ref);
495
+
420
496
  let promises = [];
421
497
  for (let worker of this.workers.values()) {
498
+ if (!worker.sentSharedReferences.has(ref)) {
499
+ continue;
500
+ }
501
+
502
+ worker.sentSharedReferences.delete(ref);
422
503
  promises.push(
423
504
  new Promise((resolve, reject) => {
424
505
  worker.call({
@@ -437,6 +518,24 @@ export default class WorkerFarm extends EventEmitter {
437
518
  };
438
519
  }
439
520
 
521
+ getSerializedSharedReference(ref: SharedReference): ArrayBuffer {
522
+ let cached = this.serializedSharedReferences.get(ref);
523
+ if (cached) {
524
+ return cached;
525
+ }
526
+
527
+ let value = this.sharedReferences.get(ref);
528
+ let buf = serialize(value).buffer;
529
+
530
+ // If the reference was created with the isCacheable option set to false,
531
+ // serializedSharedReferences will contain `null` as the value.
532
+ if (cached !== null) {
533
+ this.serializedSharedReferences.set(ref, buf);
534
+ }
535
+
536
+ return buf;
537
+ }
538
+
440
539
  async startProfile() {
441
540
  let promises = [];
442
541
  for (let worker of this.workers.values()) {
@@ -454,7 +553,7 @@ export default class WorkerFarm extends EventEmitter {
454
553
  );
455
554
  }
456
555
 
457
- this.profiler = new Profiler();
556
+ this.profiler = new SamplingProfiler();
458
557
 
459
558
  promises.push(this.profiler.startProfiling());
460
559
  await Promise.all(promises);
@@ -561,7 +660,7 @@ export default class WorkerFarm extends EventEmitter {
561
660
  static getNumWorkers(): number {
562
661
  return process.env.PARCEL_WORKERS
563
662
  ? parseInt(process.env.PARCEL_WORKERS, 10)
564
- : Math.ceil(cpuCount() / 2);
663
+ : Math.min(4, Math.ceil(cpuCount() / 2));
565
664
  }
566
665
 
567
666
  static isWorker(): boolean {
@@ -585,8 +684,12 @@ export default class WorkerFarm extends EventEmitter {
585
684
  return child.workerApi;
586
685
  }
587
686
 
588
- static getConcurrentCallsPerWorker(): number {
589
- return parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || 30;
687
+ static getConcurrentCallsPerWorker(
688
+ defaultValue?: number = DEFAULT_MAX_CONCURRENT_CALLS,
689
+ ): number {
690
+ return (
691
+ parseInt(process.env.PARCEL_MAX_CONCURRENT_CALLS, 10) || defaultValue
692
+ );
590
693
  }
591
694
  }
592
695
 
package/src/backend.js CHANGED
@@ -2,6 +2,9 @@
2
2
  import type {BackendType, WorkerImpl} from './types';
3
3
 
4
4
  export function detectBackend(): BackendType {
5
+ // $FlowFixMe
6
+ if (process.browser) return 'web';
7
+
5
8
  switch (process.env.PARCEL_WORKER_BACKEND) {
6
9
  case 'threads':
7
10
  case 'process':
@@ -22,6 +25,8 @@ export function getWorkerBackend(backend: BackendType): Class<WorkerImpl> {
22
25
  return require('./threads/ThreadsWorker').default;
23
26
  case 'process':
24
27
  return require('./process/ProcessWorker').default;
28
+ case 'web':
29
+ return require('./web/WebWorker').default;
25
30
  default:
26
31
  throw new Error(`Invalid backend: ${backend}`);
27
32
  }
package/src/bus.js CHANGED
@@ -7,7 +7,8 @@ class Bus extends EventEmitter {
7
7
  if (child) {
8
8
  child.workerApi.callMaster(
9
9
  {
10
- location: __filename,
10
+ // $FlowFixMe
11
+ location: process.browser ? '@parcel/workers/src/bus.js' : __filename,
11
12
  method: 'emit',
12
13
  args: [event, ...args],
13
14
  },
package/src/child.js CHANGED
@@ -10,15 +10,16 @@ import type {
10
10
  ChildImpl,
11
11
  } from './types';
12
12
  import type {Async, IDisposable} from '@parcel/types';
13
- import type {SharedReference, WorkerApi} from './WorkerFarm';
13
+ import type {SharedReference} from './WorkerFarm';
14
14
 
15
+ import * as coreWorker from './core-worker';
15
16
  import invariant from 'assert';
16
17
  import nullthrows from 'nullthrows';
17
18
  import Logger, {patchConsole, unpatchConsole} from '@parcel/logger';
18
19
  import ThrowableDiagnostic, {anyToDiagnostic} from '@parcel/diagnostic';
19
20
  import {deserialize} from '@parcel/core';
20
21
  import bus from './bus';
21
- import Profiler from './Profiler';
22
+ import {SamplingProfiler, tracer} from '@parcel/profiler';
22
23
  import _Handle from './Handle';
23
24
 
24
25
  // The import of './Handle' should really be imported eagerly (with @babel/plugin-transform-modules-commonjs's lazy mode).
@@ -37,17 +38,19 @@ export class Child {
37
38
  responseId: number = 0;
38
39
  responseQueue: Map<number, ChildCall> = new Map();
39
40
  loggerDisposable: IDisposable;
41
+ tracerDisposable: IDisposable;
40
42
  child: ChildImpl;
41
- profiler: ?Profiler;
42
- workerApi: WorkerApi;
43
+ profiler: ?SamplingProfiler;
43
44
  handles: Map<number, Handle> = new Map();
44
45
  sharedReferences: Map<SharedReference, mixed> = new Map();
45
46
  sharedReferencesByValue: Map<mixed, SharedReference> = new Map();
46
47
 
47
48
  constructor(ChildBackend: Class<ChildImpl>) {
48
49
  this.child = new ChildBackend(
49
- this.messageListener.bind(this),
50
- this.handleEnd.bind(this),
50
+ m => {
51
+ this.messageListener(m);
52
+ },
53
+ () => this.handleEnd(),
51
54
  );
52
55
 
53
56
  // Monitior all logging events inside this child process and forward to
@@ -55,6 +58,10 @@ export class Child {
55
58
  this.loggerDisposable = Logger.onLog(event => {
56
59
  bus.emit('logEvent', event);
57
60
  });
61
+ // .. and do the same for trace events
62
+ this.tracerDisposable = tracer.onTrace(event => {
63
+ bus.emit('traceEvent', event);
64
+ });
58
65
  }
59
66
 
60
67
  workerApi: {|
@@ -93,10 +100,23 @@ export class Child {
93
100
  this.child.send(data);
94
101
  }
95
102
 
96
- childInit(module: string, childId: number): void {
97
- // $FlowFixMe this must be dynamic
98
- this.module = require(module);
103
+ async childInit(module: string, childId: number): Promise<void> {
104
+ // $FlowFixMe
105
+ if (process.browser) {
106
+ if (module === '@parcel/core/src/worker.js') {
107
+ this.module = coreWorker;
108
+ } else {
109
+ throw new Error('No dynamic require possible: ' + module);
110
+ }
111
+ } else {
112
+ // $FlowFixMe this must be dynamic
113
+ this.module = require(module);
114
+ }
99
115
  this.childId = childId;
116
+
117
+ if (this.module.childInit != null) {
118
+ await this.module.childInit();
119
+ }
100
120
  }
101
121
 
102
122
  async handleRequest(data: WorkerRequest): Promise<void> {
@@ -136,12 +156,16 @@ export class Child {
136
156
  unpatchConsole();
137
157
  }
138
158
 
139
- result = responseFromContent(this.childInit(moduleName, child));
159
+ if (childOptions.shouldTrace) {
160
+ tracer.enable();
161
+ }
162
+
163
+ result = responseFromContent(await this.childInit(moduleName, child));
140
164
  } catch (e) {
141
165
  result = errorResponseFromError(e);
142
166
  }
143
167
  } else if (method === 'startProfile') {
144
- this.profiler = new Profiler();
168
+ this.profiler = new SamplingProfiler();
145
169
  try {
146
170
  result = responseFromContent(await this.profiler.startProfiling());
147
171
  } catch (e) {
@@ -158,7 +182,6 @@ export class Child {
158
182
  try {
159
183
  let v8 = require('v8');
160
184
  result = responseFromContent(
161
- // $FlowFixMe
162
185
  v8.writeHeapSnapshot(
163
186
  'heap-' +
164
187
  args[0] +
@@ -285,6 +308,7 @@ export class Child {
285
308
 
286
309
  handleEnd(): void {
287
310
  this.loggerDisposable.dispose();
311
+ this.tracerDisposable.dispose();
288
312
  }
289
313
 
290
314
  createReverseHandle(fn: (...args: Array<any>) => mixed): Handle {
@@ -0,0 +1,3 @@
1
+ // @flow
2
+ // eslint-disable-next-line monorepo/no-internal-import
3
+ module.exports = require('@parcel/core/src/worker.js');
@@ -0,0 +1,2 @@
1
+ // This is used only in browser builds
2
+ module.exports = {};
package/src/cpuCount.js CHANGED
@@ -47,14 +47,23 @@ export default function getCores(bypassCache?: boolean = false): number {
47
47
  return cores;
48
48
  }
49
49
 
50
- try {
51
- cores = detectRealCores();
52
- } catch (e) {
53
- // Guess the amount of real cores
54
- cores = os
55
- .cpus()
56
- .filter((cpu, index) => !cpu.model.includes('Intel') || index % 2 === 1)
57
- .length;
50
+ // $FlowFixMe
51
+ if (process.browser) {
52
+ // eslint-disable-next-line no-undef
53
+ cores = navigator.hardwareConcurrency / 2;
54
+ }
55
+
56
+ if (!cores) {
57
+ try {
58
+ cores = detectRealCores();
59
+ } catch (e) {
60
+ // Guess the amount of real cores
61
+ cores = os
62
+ .cpus()
63
+ .filter(
64
+ (cpu, index) => !cpu.model.includes('Intel') || index % 2 === 1,
65
+ ).length;
66
+ }
58
67
  }
59
68
 
60
69
  // Another fallback
package/src/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // @flow
2
- import type {LogEvent} from '@parcel/types';
2
+ import type {TraceEvent, LogEvent} from '@parcel/types';
3
3
  import invariant from 'assert';
4
4
  import WorkerFarm from './WorkerFarm';
5
5
  import Logger from '@parcel/logger';
6
6
  import bus from './bus';
7
+ import {tracer} from '@parcel/profiler';
7
8
 
8
9
  if (!WorkerFarm.isWorker()) {
9
10
  // Forward all logger events originating from workers into the main process
@@ -29,6 +30,11 @@ if (!WorkerFarm.isWorker()) {
29
30
  throw new Error('Unknown log level');
30
31
  }
31
32
  });
33
+
34
+ // Forward all trace events originating from workers into the main process
35
+ bus.on('traceEvent', (e: TraceEvent) => {
36
+ tracer.trace(e);
37
+ });
32
38
  }
33
39
 
34
40
  export default WorkerFarm;
@@ -37,6 +37,7 @@ export default class ProcessChild implements ChildImpl {
37
37
  let processSend = nullthrows(process.send).bind(process);
38
38
  processSend(serialize(data).toString('base64'), err => {
39
39
  if (err && err instanceof Error) {
40
+ // $FlowFixMe[prop-missing]
40
41
  if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {
41
42
  // IPC connection closed
42
43
  // no need to keep the worker running if it can't send or receive data
package/src/types.js CHANGED
@@ -65,4 +65,4 @@ export interface ChildImpl {
65
65
  send(data: WorkerMessage): void;
66
66
  }
67
67
 
68
- export type BackendType = 'threads' | 'process';
68
+ export type BackendType = 'threads' | 'process' | 'web';
@@ -0,0 +1,50 @@
1
+ // @flow
2
+ /* eslint-env worker*/
3
+
4
+ import type {
5
+ ChildImpl,
6
+ MessageHandler,
7
+ ExitHandler,
8
+ WorkerMessage,
9
+ } from '../types';
10
+ import {setChild} from '../childState';
11
+ import {Child} from '../child';
12
+ import {prepareForSerialization, restoreDeserializedObject} from '@parcel/core';
13
+
14
+ export default class WebChild implements ChildImpl {
15
+ onMessage: MessageHandler;
16
+ onExit: ExitHandler;
17
+
18
+ constructor(onMessage: MessageHandler, onExit: ExitHandler) {
19
+ if (
20
+ !(
21
+ typeof WorkerGlobalScope !== 'undefined' &&
22
+ self instanceof WorkerGlobalScope
23
+ )
24
+ ) {
25
+ throw new Error('Only create WebChild instances in a worker!');
26
+ }
27
+
28
+ this.onMessage = onMessage;
29
+ this.onExit = onExit;
30
+ self.addEventListener('message', ({data}: MessageEvent) => {
31
+ if (data === 'stop') {
32
+ this.onExit(0);
33
+ self.postMessage('stopped');
34
+ }
35
+ // $FlowFixMe assume WorkerMessage as data
36
+ this.handleMessage(data);
37
+ });
38
+ self.postMessage('online');
39
+ }
40
+
41
+ handleMessage(data: WorkerMessage) {
42
+ this.onMessage(restoreDeserializedObject(data));
43
+ }
44
+
45
+ send(data: WorkerMessage) {
46
+ self.postMessage(prepareForSerialization(data));
47
+ }
48
+ }
49
+
50
+ setChild(new Child(WebChild));