@parcel/core
Version:
154 lines (136 loc) • 4.47 kB
JavaScript
// @flow strict-local
import type {ReporterEvent, Reporter} from '@parcel/types';
import type {WorkerApi} from '@parcel/workers';
import type {Bundle as InternalBundle, ParcelOptions} from './types';
import type {LoadedPlugin} from './ParcelConfig';
import invariant from 'assert';
import {
bundleToInternalBundle,
bundleToInternalBundleGraph,
NamedBundle,
} from './public/Bundle';
import WorkerFarm, {bus} from '@parcel/workers';
import ParcelConfig from './ParcelConfig';
import logger, {
patchConsole,
unpatchConsole,
PluginLogger,
INTERNAL_ORIGINAL_CONSOLE,
} from '@parcel/logger';
import PluginOptions from './public/PluginOptions';
import BundleGraph from './BundleGraph';
import {tracer, PluginTracer} from '@parcel/profiler';
type Opts = {|
config: ParcelConfig,
options: ParcelOptions,
workerFarm: WorkerFarm,
|};
const instances: Set<ReporterRunner> = new Set();
export default class ReporterRunner {
workerFarm: WorkerFarm;
config: ParcelConfig;
options: ParcelOptions;
pluginOptions: PluginOptions;
reporters: Array<LoadedPlugin<Reporter>>;
constructor(opts: Opts) {
this.config = opts.config;
this.options = opts.options;
this.workerFarm = opts.workerFarm;
this.pluginOptions = new PluginOptions(this.options);
logger.onLog(event => this.report(event));
tracer.onTrace(event => this.report(event));
bus.on('reporterEvent', this.eventHandler);
instances.add(this);
if (this.options.shouldPatchConsole) {
patchConsole();
} else {
unpatchConsole();
}
}
eventHandler: ReporterEvent => void = (event): void => {
if (
event.type === 'buildProgress' &&
(event.phase === 'optimizing' || event.phase === 'packaging') &&
!(event.bundle instanceof NamedBundle)
) {
// $FlowFixMe[prop-missing]
let bundleGraphRef = event.bundleGraphRef;
// $FlowFixMe[incompatible-exact]
let bundle: InternalBundle = event.bundle;
// Convert any internal bundles back to their public equivalents as reporting
// is public api
let bundleGraph = this.workerFarm.workerApi.getSharedReference(
// $FlowFixMe
bundleGraphRef,
);
invariant(bundleGraph instanceof BundleGraph);
// $FlowFixMe[incompatible-call]
this.report({
...event,
bundle: NamedBundle.get(bundle, bundleGraph, this.options),
});
return;
}
this.report(event);
};
async report(event: ReporterEvent) {
// We should catch all errors originating from reporter plugins to prevent infinite loops
try {
let reporters = this.reporters;
if (!reporters) {
this.reporters = await this.config.getReporters();
reporters = this.reporters;
}
for (let reporter of this.reporters) {
let measurement;
try {
// To avoid an infinite loop we don't measure trace events, as they'll
// result in another trace!
if (event.type !== 'trace') {
measurement = tracer.createMeasurement(reporter.name, 'reporter');
}
await reporter.plugin.report({
event,
options: this.pluginOptions,
logger: new PluginLogger({origin: reporter.name}),
tracer: new PluginTracer({
origin: reporter.name,
category: 'reporter',
}),
});
} catch (reportError) {
INTERNAL_ORIGINAL_CONSOLE.error(reportError);
} finally {
measurement && measurement.end();
}
}
} catch (err) {
INTERNAL_ORIGINAL_CONSOLE.error(err);
}
}
dispose() {
bus.off('reporterEvent', this.eventHandler);
instances.delete(this);
}
}
export function reportWorker(workerApi: WorkerApi, event: ReporterEvent) {
if (
event.type === 'buildProgress' &&
(event.phase === 'optimizing' || event.phase === 'packaging')
) {
// Convert any public api bundles to their internal equivalents for
// easy serialization
bus.emit('reporterEvent', {
...event,
bundle: bundleToInternalBundle(event.bundle),
bundleGraphRef: workerApi.resolveSharedReference(
bundleToInternalBundleGraph(event.bundle),
),
});
return;
}
bus.emit('reporterEvent', event);
}
export async function report(event: ReporterEvent): Promise<void> {
await Promise.all([...instances].map(instance => instance.report(event)));
}