1 |
|
2 |
|
3 | import type {
|
4 | AsyncSubscription,
|
5 | BundleGraph as IBundleGraph,
|
6 | BuildEvent,
|
7 | EnvironmentOpts,
|
8 | FilePath,
|
9 | InitialParcelOptions,
|
10 | ModuleSpecifier,
|
11 | } from '@parcel/types';
|
12 | import type {ParcelOptions} from './types';
|
13 | import type {FarmOptions} from '@parcel/workers';
|
14 | import type {Diagnostic} from '@parcel/diagnostic';
|
15 | import type {AbortSignal} from 'abortcontroller-polyfill/dist/cjs-ponyfill';
|
16 |
|
17 | import invariant from 'assert';
|
18 | import ThrowableDiagnostic, {anyToDiagnostic} from '@parcel/diagnostic';
|
19 | import {createDependency} from './Dependency';
|
20 | import {createEnvironment} from './Environment';
|
21 | import {assetFromValue} from './public/Asset';
|
22 | import BundleGraph from './public/BundleGraph';
|
23 | import BundlerRunner from './BundlerRunner';
|
24 | import WorkerFarm from '@parcel/workers';
|
25 | import nullthrows from 'nullthrows';
|
26 | import path from 'path';
|
27 | import AssetGraphBuilder from './AssetGraphBuilder';
|
28 | import {assertSignalNotAborted, BuildAbortError} from './utils';
|
29 | import PackagerRunner from './PackagerRunner';
|
30 | import loadParcelConfig from './loadParcelConfig';
|
31 | import ReporterRunner, {report} from './ReporterRunner';
|
32 | import dumpGraphToGraphViz from './dumpGraphToGraphViz';
|
33 | import resolveOptions from './resolveOptions';
|
34 | import {ValueEmitter} from '@parcel/events';
|
35 | import {registerCoreWithSerializer} from './utils';
|
36 | import {createCacheDir} from '@parcel/cache';
|
37 | import {AbortController} from 'abortcontroller-polyfill/dist/cjs-ponyfill';
|
38 | import {PromiseQueue} from '@parcel/utils';
|
39 |
|
40 | registerCoreWithSerializer();
|
41 |
|
42 | export const INTERNAL_TRANSFORM = Symbol('internal_transform');
|
43 | export const INTERNAL_RESOLVE = Symbol('internal_resolve');
|
44 |
|
45 | export 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 |
|
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 |
|
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 |
|
201 |
|
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 |
|
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 |
|
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 |
|
376 | export class BuildError extends ThrowableDiagnostic {
|
377 | constructor(diagnostics: Array<Diagnostic>) {
|
378 | super({diagnostic: diagnostics});
|
379 |
|
380 | this.name = 'BuildError';
|
381 | }
|
382 | }
|
383 |
|
384 | export {default as Asset} from './InternalAsset';
|
385 |
|
386 | export function createWorkerFarm(options: $Shape<FarmOptions> = {}) {
|
387 | return new WorkerFarm({
|
388 | ...options,
|
389 | workerPath: require.resolve('./worker'),
|
390 | });
|
391 | }
|