UNPKG

10.6 kBJavaScriptView Raw
1// @flow strict-local
2
3import type {
4 AsyncSubscription,
5 BundleGraph as IBundleGraph,
6 BuildEvent,
7 EnvironmentOpts,
8 FilePath,
9 InitialParcelOptions,
10 ModuleSpecifier,
11} from '@parcel/types';
12import type {ParcelOptions} from './types';
13import type {FarmOptions} from '@parcel/workers';
14import type {Diagnostic} from '@parcel/diagnostic';
15import type {AbortSignal} from 'abortcontroller-polyfill/dist/cjs-ponyfill';
16
17import invariant from 'assert';
18import ThrowableDiagnostic, {anyToDiagnostic} from '@parcel/diagnostic';
19import {createDependency} from './Dependency';
20import {createEnvironment} from './Environment';
21import {assetFromValue} from './public/Asset';
22import BundleGraph from './public/BundleGraph';
23import BundlerRunner from './BundlerRunner';
24import WorkerFarm from '@parcel/workers';
25import nullthrows from 'nullthrows';
26import path from 'path';
27import AssetGraphBuilder from './AssetGraphBuilder';
28import {assertSignalNotAborted, BuildAbortError} from './utils';
29import PackagerRunner from './PackagerRunner';
30import loadParcelConfig from './loadParcelConfig';
31import ReporterRunner, {report} from './ReporterRunner';
32import dumpGraphToGraphViz from './dumpGraphToGraphViz';
33import resolveOptions from './resolveOptions';
34import {ValueEmitter} from '@parcel/events';
35import {registerCoreWithSerializer} from './utils';
36import {createCacheDir} from '@parcel/cache';
37import {AbortController} from 'abortcontroller-polyfill/dist/cjs-ponyfill';
38import {PromiseQueue} from '@parcel/utils';
39
40registerCoreWithSerializer();
41
42export const INTERNAL_TRANSFORM = Symbol('internal_transform');
43export const INTERNAL_RESOLVE = Symbol('internal_resolve');
44
45export default class Parcel {
46 #assetGraphBuilder; // AssetGraphBuilder
47 #runtimesAssetGraphBuilder; // AssetGraphBuilder
48 #bundlerRunner; // BundlerRunner
49 #packagerRunner; // PackagerRunner
50 #config;
51 #farm; // WorkerFarm
52 #initialized = false; // boolean
53 #initialOptions; // InitialParcelOptions;
54 #reporterRunner; // ReporterRunner
55 #resolvedOptions = null; // ?ParcelOptions
56 #runPackage; // (bundle: IBundle, bundleGraph: InternalBundleGraph) => Promise<Stats>;
57 #watchAbortController; // AbortController
58 #watchQueue = new PromiseQueue<?BuildEvent>({maxConcurrent: 1}); // PromiseQueue<?BuildEvent>
59 #watchEvents = new ValueEmitter<
60 | {|
61 +error: Error,
62 +buildEvent?: void,
63 |}
64 | {|
65 +buildEvent: BuildEvent,
66 +error?: void,
67 |},
68 >();
69 #watcherSubscription; // AsyncSubscription
70 #watcherCount = 0; // number
71
72 constructor(options: InitialParcelOptions) {
73 this.#initialOptions = options;
74 }
75
76 async init(): Promise<void> {
77 if (this.#initialized) {
78 return;
79 }
80
81 let resolvedOptions: ParcelOptions = await resolveOptions(
82 this.#initialOptions,
83 );
84 this.#resolvedOptions = resolvedOptions;
85 await createCacheDir(resolvedOptions.outputFS, resolvedOptions.cacheDir);
86
87 let {config} = await loadParcelConfig(
88 path.join(resolvedOptions.inputFS.cwd(), 'index'),
89 resolvedOptions,
90 );
91 this.#config = config;
92 this.#farm =
93 this.#initialOptions.workerFarm ??
94 createWorkerFarm({
95 patchConsole: resolvedOptions.patchConsole,
96 });
97
98 this.#assetGraphBuilder = new AssetGraphBuilder();
99 this.#runtimesAssetGraphBuilder = new AssetGraphBuilder();
100
101 await Promise.all([
102 this.#assetGraphBuilder.init({
103 name: 'MainAssetGraph',
104 options: resolvedOptions,
105 config,
106 entries: resolvedOptions.entries,
107 workerFarm: this.#farm,
108 }),
109 this.#runtimesAssetGraphBuilder.init({
110 name: 'RuntimesAssetGraph',
111 options: resolvedOptions,
112 config,
113 workerFarm: this.#farm,
114 }),
115 ]);
116
117 this.#bundlerRunner = new BundlerRunner({
118 options: resolvedOptions,
119 runtimesBuilder: this.#runtimesAssetGraphBuilder,
120 config,
121 workerFarm: this.#farm,
122 });
123
124 this.#reporterRunner = new ReporterRunner({
125 config,
126 options: resolvedOptions,
127 workerFarm: this.#farm,
128 });
129
130 this.#packagerRunner = new PackagerRunner({
131 config,
132 farm: this.#farm,
133 options: resolvedOptions,
134 report,
135 });
136
137 this.#runPackage = this.#farm.createHandle('runPackage');
138 this.#initialized = true;
139 }
140
141 async run(): Promise<IBundleGraph> {
142 let startTime = Date.now();
143 if (!this.#initialized) {
144 await this.init();
145 }
146
147 let result = await this.build({startTime});
148 await Promise.all([
149 this.#assetGraphBuilder.writeToCache(),
150 this.#runtimesAssetGraphBuilder.writeToCache(),
151 ]);
152
153 if (!this.#initialOptions.workerFarm) {
154 // If there wasn't a workerFarm passed in, we created it. End the farm.
155 await this.#farm.end();
156 }
157
158 if (result.type === 'buildFailure') {
159 throw new BuildError(result.diagnostics);
160 }
161
162 return result.bundleGraph;
163 }
164
165 async startNextBuild() {
166 this.#watchAbortController = new AbortController();
167
168 try {
169 this.#watchEvents.emit({
170 buildEvent: await this.build({
171 signal: this.#watchAbortController.signal,
172 }),
173 });
174 } catch (err) {
175 // Ignore BuildAbortErrors and only emit critical errors.
176 if (!(err instanceof BuildAbortError)) {
177 throw err;
178 }
179 }
180 }
181
182 async watch(
183 cb?: (err: ?Error, buildEvent?: BuildEvent) => mixed,
184 ): Promise<AsyncSubscription> {
185 let watchEventsDisposable;
186 if (cb) {
187 watchEventsDisposable = this.#watchEvents.addListener(
188 ({error, buildEvent}) => cb(error, buildEvent),
189 );
190 }
191
192 if (this.#watcherCount === 0) {
193 if (!this.#initialized) {
194 await this.init();
195 }
196
197 this.#watcherSubscription = await this._getWatcherSubscription();
198 await this.#reporterRunner.report({type: 'watchStart'});
199
200 // Kick off a first build, but don't await its results. Its results will
201 // be provided to the callback.
202 this.#watchQueue.add(() => this.startNextBuild());
203 this.#watchQueue.run();
204 }
205
206 this.#watcherCount++;
207
208 let unsubscribePromise;
209 const unsubscribe = async () => {
210 if (watchEventsDisposable) {
211 watchEventsDisposable.dispose();
212 }
213
214 this.#watcherCount--;
215 if (this.#watcherCount === 0) {
216 await nullthrows(this.#watcherSubscription).unsubscribe();
217 this.#watcherSubscription = null;
218 await this.#reporterRunner.report({type: 'watchEnd'});
219 await Promise.all([
220 this.#assetGraphBuilder.writeToCache(),
221 this.#runtimesAssetGraphBuilder.writeToCache(),
222 ]);
223 }
224 };
225
226 return {
227 unsubscribe() {
228 if (unsubscribePromise == null) {
229 unsubscribePromise = unsubscribe();
230 }
231
232 return unsubscribePromise;
233 },
234 };
235 }
236
237 async build({
238 signal,
239 startTime = Date.now(),
240 }: {|
241 signal?: AbortSignal,
242 startTime?: number,
243 |}): Promise<BuildEvent> {
244 let options = nullthrows(this.#resolvedOptions);
245 try {
246 if (options.profile) {
247 await this.#farm.startProfile();
248 }
249
250 this.#reporterRunner.report({
251 type: 'buildStart',
252 });
253
254 let {assetGraph, changedAssets} = await this.#assetGraphBuilder.build(
255 signal,
256 );
257 dumpGraphToGraphViz(assetGraph, 'MainAssetGraph');
258
259 let bundleGraph = await this.#bundlerRunner.bundle(assetGraph, {signal});
260 dumpGraphToGraphViz(bundleGraph._graph, 'BundleGraph');
261
262 await this.#packagerRunner.writeBundles(bundleGraph);
263 assertSignalNotAborted(signal);
264
265 let event = {
266 type: 'buildSuccess',
267 changedAssets: new Map(
268 Array.from(changedAssets).map(([id, asset]) => [
269 id,
270 assetFromValue(asset, options),
271 ]),
272 ),
273 bundleGraph: new BundleGraph(bundleGraph, options),
274 buildTime: Date.now() - startTime,
275 };
276 this.#reporterRunner.report(event);
277
278 await this.#assetGraphBuilder.validate();
279
280 return event;
281 } catch (e) {
282 if (e instanceof BuildAbortError) {
283 throw e;
284 }
285
286 let diagnostic = anyToDiagnostic(e);
287 let event = {
288 type: 'buildFailure',
289 diagnostics: Array.isArray(diagnostic) ? diagnostic : [diagnostic],
290 };
291
292 await this.#reporterRunner.report(event);
293
294 return event;
295 } finally {
296 if (options.profile) {
297 await this.#farm.endProfile();
298 }
299 }
300 }
301
302 // $FlowFixMe
303 async [INTERNAL_TRANSFORM]({
304 filePath,
305 env,
306 code,
307 }: {|
308 filePath: FilePath,
309 env: EnvironmentOpts,
310 code?: string,
311 |}) {
312 let [result] = await Promise.all([
313 this.#assetGraphBuilder.runTransform({
314 filePath,
315 code,
316 env: createEnvironment(env),
317 }),
318 this.#reporterRunner.config.getReporters(),
319 ]);
320
321 return result;
322 }
323
324 // $FlowFixMe
325 async [INTERNAL_RESOLVE]({
326 moduleSpecifier,
327 sourcePath,
328 env,
329 }: {|
330 moduleSpecifier: ModuleSpecifier,
331 sourcePath: FilePath,
332 env: EnvironmentOpts,
333 |}): Promise<FilePath> {
334 let resolved = await this.#assetGraphBuilder.resolverRunner.resolve(
335 createDependency({
336 moduleSpecifier,
337 sourcePath,
338 env: createEnvironment(env),
339 }),
340 );
341
342 return resolved.filePath;
343 }
344
345 _getWatcherSubscription(): Promise<AsyncSubscription> {
346 invariant(this.#watcherSubscription == null);
347
348 let resolvedOptions = nullthrows(this.#resolvedOptions);
349 let opts = this.#assetGraphBuilder.getWatcherOptions();
350
351 return resolvedOptions.inputFS.watch(
352 resolvedOptions.projectRoot,
353 (err, _events) => {
354 let events = _events.filter(e => !e.path.includes('.cache'));
355
356 if (err) {
357 this.#watchEvents.emit({error: err});
358 return;
359 }
360
361 let isInvalid = this.#assetGraphBuilder.respondToFSEvents(events);
362 if (isInvalid && this.#watchQueue.getNumWaiting() === 0) {
363 if (this.#watchAbortController) {
364 this.#watchAbortController.abort();
365 }
366
367 this.#watchQueue.add(() => this.startNextBuild());
368 this.#watchQueue.run();
369 }
370 },
371 opts,
372 );
373 }
374}
375
376export class BuildError extends ThrowableDiagnostic {
377 constructor(diagnostics: Array<Diagnostic>) {
378 super({diagnostic: diagnostics});
379
380 this.name = 'BuildError';
381 }
382}
383
384export {default as Asset} from './InternalAsset';
385
386export function createWorkerFarm(options: $Shape<FarmOptions> = {}) {
387 return new WorkerFarm({
388 ...options,
389 workerPath: require.resolve('./worker'),
390 });
391}