Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package on main thread for small rebuilds #8491

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 25 additions & 5 deletions packages/core/core/src/PackagerRunner.js
Expand Up @@ -68,6 +68,7 @@ type Opts = {|
report: ReportFn,
previousDevDeps: Map<string, string>,
previousInvalidations: Array<RequestInvalidation>,
useFarm: boolean,
|};

export type PackageRequestResult = {|
Expand Down Expand Up @@ -110,20 +111,23 @@ export default class PackagerRunner {
devDepRequests: Map<string, DevDepRequest>;
invalidations: Map<string, RequestInvalidation>;
previousInvalidations: Array<RequestInvalidation>;
useFarm: boolean;

constructor({
config,
options,
report,
previousDevDeps,
previousInvalidations,
useFarm,
}: Opts) {
this.config = config;
this.options = options;
this.report = report;
this.previousDevDeps = previousDevDeps;
this.devDepRequests = new Map();
this.previousInvalidations = previousInvalidations;
this.useFarm = useFarm;
this.invalidations = new Map();
this.pluginOptions = new PluginOptions(
optionsProxy(this.options, option => {
Expand Down Expand Up @@ -386,6 +390,19 @@ export default class PackagerRunner {
bundleConfigs: Map<string, Config>,
): Promise<BundleResult> {
let bundle = NamedBundle.get(internalBundle, bundleGraph, this.options);
let publicBundleGraph = new BundleGraph<NamedBundleType>(
bundleGraph,
NamedBundle.get.bind(NamedBundle),
this.options,
);

if (this.useFarm) {
this.report({
type: 'bundleGraph',
bundleGraph: publicBundleGraph,
});
}

this.report({
type: 'buildProgress',
phase: 'packaging',
Expand All @@ -399,11 +416,7 @@ export default class PackagerRunner {
config: configs.get(name)?.result,
bundleConfig: bundleConfigs.get(name)?.result,
bundle,
bundleGraph: new BundleGraph<NamedBundleType>(
bundleGraph,
NamedBundle.get.bind(NamedBundle),
this.options,
),
bundleGraph: publicBundleGraph,
getSourceMapReference: map => {
return this.getSourceMapReference(bundle, map);
},
Expand Down Expand Up @@ -482,6 +495,13 @@ export default class PackagerRunner {
return {type: bundle.type, contents, map};
}

if (this.useFarm) {
this.report({
type: 'bundleGraph',
bundleGraph,
});
}

this.report({
type: 'buildProgress',
phase: 'optimizing',
Expand Down
30 changes: 24 additions & 6 deletions packages/core/core/src/ReporterRunner.js
Expand Up @@ -21,6 +21,7 @@ import logger, {
} from '@parcel/logger';
import PluginOptions from './public/PluginOptions';
import BundleGraph from './BundleGraph';
import {bundleGraphToInternalBundleGraph} from './public/BundleGraph';

type Opts = {|
config: ParcelConfig,
Expand All @@ -34,6 +35,7 @@ export default class ReporterRunner {
options: ParcelOptions;
pluginOptions: PluginOptions;
reporters: Array<LoadedPlugin<Reporter>>;
bundleGraph: BundleGraph;

constructor(opts: Opts) {
this.config = opts.config;
Expand All @@ -53,7 +55,9 @@ export default class ReporterRunner {
}

eventHandler: ReporterEvent => void = (event): void => {
if (
if (event.type === 'bundleGraph') {
this.bundleGraph = bundleGraphToInternalBundleGraph(event.bundleGraph);
} else if (
event.type === 'buildProgress' &&
(event.phase === 'optimizing' || event.phase === 'packaging') &&
!(event.bundle instanceof NamedBundle)
Expand All @@ -64,10 +68,12 @@ export default class ReporterRunner {
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,
);
let bundleGraph =
this.bundleGraph ??
this.workerFarm.workerApi.getSharedReference(
// $FlowFixMe
bundleGraphRef,
);
invariant(bundleGraph instanceof BundleGraph);
// $FlowFixMe[incompatible-call]
this.report({
Expand Down Expand Up @@ -131,5 +137,17 @@ export function reportWorker(workerApi: WorkerApi, event: ReporterEvent) {
}

export function report(event: ReporterEvent) {
bus.emit('reporterEvent', event);
if (
event.bundleGraph &&
event.type === 'buildProgress' &&
(event.phase === 'optimizing' || event.phase === 'packaging')
) {
bus.emit('reporterEvent', {
...event,
bundle: bundleToInternalBundle(event.bundle),
bundleGraphRef: event.bundleGraph,
});
} else {
bus.emit('reporterEvent', event);
}
}
11 changes: 7 additions & 4 deletions packages/core/core/src/requests/PackageRequest.js
Expand Up @@ -18,8 +18,9 @@ import createParcelConfigRequest from './ParcelConfigRequest';
type PackageRequestInput = {|
bundleGraph: BundleGraph,
bundle: Bundle,
bundleGraphReference: SharedReference,
bundleGraphReference?: SharedReference,
optionsRef: SharedReference,
shouldSerialize?: boolean,
|};

type RunInput = {|
Expand All @@ -46,18 +47,20 @@ export function createPackageRequest(
}

async function run({input, api, farm}: RunInput) {
let {bundleGraphReference, optionsRef, bundle} = input;
let runPackage = farm.createHandle('runPackage');
let {bundleGraphReference, optionsRef, bundle, shouldSerialize} = input;
let runPackage = farm.createHandle('runPackage', shouldSerialize);

let start = Date.now();
let {devDeps, invalidDevDeps} = await getDevDepRequests(api);
let {cachePath} = nullthrows(
await api.runRequest<null, ConfigAndCachePath>(createParcelConfigRequest()),
);

let {devDepRequests, configRequests, bundleInfo, invalidations} =
(await runPackage({
bundle,
bundleGraphReference,
bundleGraphReference:
bundleGraphReference == null ? input.bundleGraph : bundleGraphReference,
optionsRef,
configCachePath: cachePath,
previousDevDeps: devDeps,
Expand Down
26 changes: 21 additions & 5 deletions packages/core/core/src/requests/WriteBundlesRequest.js
Expand Up @@ -49,10 +49,6 @@ export default function createWriteBundlesRequest(

async function run({input, api, farm, options}: RunInput) {
let {bundleGraph, optionsRef} = input;
let {ref, dispose} = await farm.createSharedReference(
bundleGraph,
serialize(bundleGraph),
);

api.invalidateOnOptionChange('shouldContentHash');

Expand Down Expand Up @@ -83,6 +79,23 @@ async function run({input, api, farm, options}: RunInput) {
return true;
});

let ref;
let dispose;

// Create shared reference in WorkerFarm if we need to change multiple bundles and if we
// can't skip the subrequests for all the bundles
let shouldSerialize = false;
if (
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I got this logic quite right.. are there any other cases to look for here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure either, but I'm confused that the comment says "can't skip all the bundles = at least 1 bundle can't be skipped" but then the if condition checks "there are at least 2 bundles which can't be skipped".

bundles.length > 1 &&
bundles.filter(b => !api.canSkipSubrequest(bundleGraph.getHash(b))).length >
1
) {
shouldSerialize = true;
({ref, dispose} = await farm.createSharedReference(
bundleGraph,
serialize(bundleGraph),
));
}
try {
await Promise.all(
bundles.map(async bundle => {
Expand All @@ -91,6 +104,7 @@ async function run({input, api, farm, options}: RunInput) {
bundleGraph,
bundleGraphReference: ref,
optionsRef,
shouldSerialize,
});

let info = await api.runRequest(request);
Expand Down Expand Up @@ -134,7 +148,9 @@ async function run({input, api, farm, options}: RunInput) {
api.storeResult(res);
return res;
} finally {
await dispose();
if (dispose) {
await dispose();
}
}
}

Expand Down
15 changes: 11 additions & 4 deletions packages/core/core/src/worker.js
Expand Up @@ -17,7 +17,7 @@ import Transformation, {
type TransformationOpts,
type TransformationResult,
} from './Transformation';
import {reportWorker} from './ReporterRunner';
import {reportWorker, report} from './ReporterRunner';
import PackagerRunner, {type PackageRequestResult} from './PackagerRunner';
import Validation, {type ValidationOpts} from './Validation';
import ParcelConfig from './ParcelConfig';
Expand Down Expand Up @@ -121,25 +121,32 @@ export async function runPackage(
previousInvalidations,
}: {|
bundle: Bundle,
bundleGraphReference: SharedReference,
bundleGraphReference: SharedReference | BundleGraph,
configCachePath: string,
optionsRef: SharedReference,
previousDevDeps: Map<string, string>,
invalidDevDeps: Array<DevDepSpecifier>,
previousInvalidations: Array<RequestInvalidation>,
|},
): Promise<PackageRequestResult> {
let bundleGraph = workerApi.getSharedReference(bundleGraphReference);
let bundleGraph =
typeof bundleGraphReference === 'number'
? workerApi.getSharedReference(bundleGraphReference)
: bundleGraphReference;
invariant(bundleGraph instanceof BundleGraph);
let options = loadOptions(optionsRef, workerApi);
let parcelConfig = await loadConfig(configCachePath, options);

let runner = new PackagerRunner({
config: parcelConfig,
options,
report: reportWorker.bind(null, workerApi),
report:
typeof bundleGraphReference === 'number'
? reportWorker.bind(null, workerApi)
: report,
previousDevDeps,
previousInvalidations,
useFarm: typeof bundleGraphReference === 'number' ? true : false,
});

return runner.run(bundleGraph, bundle, invalidDevDeps);
Expand Down
14 changes: 12 additions & 2 deletions packages/core/types/index.js
Expand Up @@ -1709,7 +1709,11 @@ export type TextLogEvent = {|
/**
* @section reporter
*/
export type LogEvent = ProgressLogEvent | DiagnosticLogEvent | TextLogEvent;
export type LogEvent =
| ProgressLogEvent
| DiagnosticLogEvent
| TextLogEvent
| BundleGraphEvent;

/**
* The build just started.
Expand Down Expand Up @@ -1764,6 +1768,11 @@ export type BundlingProgressEvent = {|
+phase: 'bundling',
|};

export type BundleGraphEvent = {|
+type: 'bundleGraph',
+bundleGraph: BundleGraph<NamedBundle>,
|};
Comment on lines +1772 to +1775
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What Devon meant in #8491 (comment) is that there could an internal event between PackagerRunner and ReporterRunner (which the reporter runner then just uses for setting the bundle graph but doesn't send it out to the reporter plugins).

But I think it's strange to have to split it up into two events in the first place.
Why don't you use the same idea of passing either the bundlegraph ref or the bundlegraph object itself in the packaging event? The PackagerRunnner is already running on the main thread, so calling report(something) wouldn't perform any serialization anyway.


/**
* A new Bundle is being packaged.
* @section reporter
Expand Down Expand Up @@ -1840,7 +1849,8 @@ export type ReporterEvent =
| BuildFailureEvent
| WatchStartEvent
| WatchEndEvent
| ValidationEvent;
| ValidationEvent
| BundleGraphEvent;

/**
* @section reporter
Expand Down
18 changes: 13 additions & 5 deletions packages/core/workers/src/WorkerFarm.js
Expand Up @@ -175,21 +175,29 @@ export default class WorkerFarm extends EventEmitter {
);
}

createHandle(method: string): HandleFunction {
createHandle(
method: string,
shouldSerialize: boolean = true,
): HandleFunction {
return async (...args) => {
// Child process workers are slow to start (~600ms).
// While we're waiting, just run on the main thread.
// This significantly speeds up startup time.
if (this.shouldUseRemoteWorkers()) {
if (this.shouldUseRemoteWorkers() && shouldSerialize) {
return this.addCall(method, [...args, false]);
} else {
if (this.options.warmWorkers && this.shouldStartRemoteWorkers()) {
this.warmupWorker(method, args);
}

let processedArgs = restoreDeserializedObject(
prepareForSerialization([...args, false]),
);
let processedArgs;
if (shouldSerialize) {
processedArgs = restoreDeserializedObject(
prepareForSerialization([...args, false]),
);
} else {
processedArgs = args;
}

if (this.localWorkerInit != null) {
await this.localWorkerInit;
Expand Down