UNPKG

11.4 kBJavaScriptView Raw
1// @flow strict-local
2
3import type {FilePath} from '@parcel/types';
4import type WorkerFarm from '@parcel/workers';
5import type {
6 AssetRequestDesc,
7 AssetRequestResult,
8 Dependency,
9 Entry,
10 ParcelOptions,
11 TransformationOpts,
12} from './types';
13import type RequestTracker, {RequestRunnerAPI} from './RequestTracker';
14import type AssetGraph from './AssetGraph';
15import type ParcelConfig from './ParcelConfig';
16import type {TargetResolveResult} from './TargetResolver';
17import type {EntryResult} from './EntryResolver'; // ? Is this right
18
19import invariant from 'assert';
20import nullthrows from 'nullthrows';
21import {isGlob} from '@parcel/utils';
22import {nodeFromAssetGroup} from './AssetGraph';
23import ResolverRunner from './ResolverRunner';
24import {EntryResolver} from './EntryResolver';
25import TargetResolver from './TargetResolver';
26import {RequestRunner, generateRequestId} from './RequestTracker';
27
28export type AssetGraphBuildRequest =
29 | EntryRequest
30 | TargetRequest
31 | AssetRequest
32 | DepPathRequest;
33
34type EntryRequest = {|
35 id: string,
36 +type: 'entry_request',
37 request: FilePath,
38 result?: EntryResult,
39|};
40
41type TargetRequest = {|
42 id: string,
43 +type: 'target_request',
44 request: Entry,
45 result?: TargetResolveResult,
46|};
47
48type AssetRequest = {|
49 id: string,
50 +type: 'asset_request',
51 request: AssetRequestDesc,
52 result?: AssetRequestResult,
53|};
54
55type DependencyResult = AssetRequestDesc | null | void;
56type DepPathRequest = {|
57 id: string,
58 +type: 'dep_path_request',
59 request: Dependency,
60 result?: DependencyResult,
61|};
62
63export class EntryRequestRunner extends RequestRunner<FilePath, EntryResult> {
64 entryResolver: EntryResolver;
65 assetGraph: AssetGraph;
66
67 constructor(opts: {|
68 tracker: RequestTracker,
69 options: ParcelOptions,
70 assetGraph: AssetGraph,
71 |}) {
72 super(opts);
73 this.type = 'entry_request';
74 this.entryResolver = new EntryResolver(opts.options);
75 this.assetGraph = opts.assetGraph;
76 }
77
78 run(request: FilePath) {
79 return this.entryResolver.resolveEntry(request);
80 }
81
82 onComplete(request: FilePath, result: EntryResult, api: RequestRunnerAPI) {
83 this.assetGraph.resolveEntry(request, result.entries);
84
85 // Connect files like package.json that affect the entry
86 // resolution so we invalidate when they change.
87 for (let file of result.files) {
88 api.invalidateOnFileUpdate(file.filePath);
89 }
90
91 // If the entry specifier is a glob, add a glob node so
92 // we invalidate when a new file matches.
93 if (isGlob(request)) {
94 api.invalidateOnFileCreate(request);
95 }
96 }
97}
98
99export class TargetRequestRunner extends RequestRunner<
100 Entry,
101 TargetResolveResult,
102> {
103 targetResolver: TargetResolver;
104 assetGraph: AssetGraph;
105
106 constructor(opts: {|
107 tracker: RequestTracker,
108 options: ParcelOptions,
109 assetGraph: AssetGraph,
110 |}) {
111 super(opts);
112 this.type = 'target_request';
113 this.targetResolver = new TargetResolver(opts.options);
114 this.assetGraph = opts.assetGraph;
115 }
116
117 run(request: Entry) {
118 return this.targetResolver.resolve(request.packagePath);
119 }
120
121 onComplete(
122 request: Entry,
123 result: TargetResolveResult,
124 api: RequestRunnerAPI,
125 ) {
126 this.assetGraph.resolveTargets(request, result.targets);
127
128 // Connect files like package.json that affect the target
129 // resolution so we invalidate when they change.
130 for (let file of result.files) {
131 api.invalidateOnFileUpdate(file.filePath);
132 }
133 }
134}
135
136export class AssetRequestRunner extends RequestRunner<
137 AssetRequestDesc,
138 AssetRequestResult,
139> {
140 options: ParcelOptions;
141 runTransform: TransformationOpts => Promise<AssetRequestResult>;
142 assetGraph: AssetGraph;
143
144 constructor(opts: {|
145 tracker: RequestTracker,
146 options: ParcelOptions,
147 workerFarm: WorkerFarm,
148 assetGraph: AssetGraph,
149 |}) {
150 super(opts);
151 this.type = 'asset_request';
152 this.options = opts.options;
153 this.runTransform = opts.workerFarm.createHandle('runTransform');
154 this.assetGraph = opts.assetGraph;
155 }
156
157 async run(request: AssetRequestDesc, api: RequestRunnerAPI) {
158 api.invalidateOnFileUpdate(
159 await this.options.inputFS.realpath(request.filePath),
160 );
161 let start = Date.now();
162 let {assets, configRequests} = await this.runTransform({
163 request: request,
164 options: this.options,
165 });
166
167 let time = Date.now() - start;
168 for (let asset of assets) {
169 asset.stats.time = time;
170 }
171 return {assets, configRequests};
172 }
173
174 onComplete(
175 request: AssetRequestDesc,
176 result: AssetRequestResult,
177 api: RequestRunnerAPI,
178 ) {
179 this.assetGraph.resolveAssetGroup(request, result.assets);
180
181 let {assets, configRequests} = result;
182
183 for (let asset of assets) {
184 for (let filePath of asset.includedFiles.keys()) {
185 api.invalidateOnFileUpdate(filePath);
186 api.invalidateOnFileDelete(filePath);
187 }
188 }
189
190 // TODO: this should no longer be needed once we have ConfigRequestRunner
191 let graph = this.tracker.graph;
192 let subrequestNodes = [];
193 // Add config requests
194 for (let {request, result} of configRequests) {
195 let id = generateRequestId('config_request', request);
196 let shouldSetupInvalidations =
197 graph.invalidNodeIds.has(id) || !graph.hasNode(id);
198 let subrequestNode = nullthrows(
199 graph.addRequest({
200 id,
201 type: 'config_request',
202 request,
203 result,
204 }),
205 );
206 invariant(subrequestNode.type === 'request');
207
208 if (shouldSetupInvalidations) {
209 if (result.resolvedPath != null) {
210 graph.invalidateOnFileUpdate(subrequestNode.id, result.resolvedPath);
211 }
212
213 for (let filePath of result.includedFiles) {
214 graph.invalidateOnFileUpdate(subrequestNode.id, filePath);
215 }
216
217 if (result.watchGlob != null) {
218 graph.invalidateOnFileCreate(subrequestNode.id, result.watchGlob);
219 }
220
221 if (result.shouldInvalidateOnStartup) {
222 graph.invalidateOnStartup(subrequestNode.id);
223 }
224 }
225 subrequestNodes.push(subrequestNode);
226
227 // Add dep version requests
228 for (let [moduleSpecifier, version] of result.devDeps) {
229 let depVersionRequst = {
230 moduleSpecifier,
231 resolveFrom: result.resolvedPath, // TODO: resolveFrom should be nearest package boundary
232 };
233 let id = generateRequestId('dep_version_request', depVersionRequst);
234 let shouldSetupInvalidations =
235 graph.invalidNodeIds.has(id) || !graph.hasNode(id);
236 let subrequestNode = nullthrows(
237 graph.addRequest({
238 id,
239 type: 'dep_version_request',
240 request: depVersionRequst,
241 result: version,
242 }),
243 );
244 invariant(subrequestNode.type === 'request');
245 if (shouldSetupInvalidations) {
246 if (this.options.lockFile != null) {
247 graph.invalidateOnFileUpdate(
248 subrequestNode.id,
249 this.options.lockFile,
250 );
251 }
252 }
253 subrequestNodes.push(subrequestNode);
254 }
255 }
256
257 api.replaceSubrequests(subrequestNodes);
258
259 // TODO: add includedFiles even if it failed so we can try a rebuild if those files change
260 }
261}
262
263const invertMap = <K, V>(map: Map<K, V>): Map<V, K> =>
264 new Map([...map].map(([key, val]) => [val, key]));
265
266export class DepPathRequestRunner extends RequestRunner<
267 Dependency,
268 DependencyResult,
269> {
270 resolverRunner: ResolverRunner;
271 assetGraph: AssetGraph;
272
273 constructor(opts: {|
274 tracker: RequestTracker,
275 options: ParcelOptions,
276 config: ParcelConfig,
277 assetGraph: AssetGraph,
278 |}) {
279 super(opts);
280 this.type = 'dep_path_request';
281 let {options, config, assetGraph} = opts;
282 this.resolverRunner = new ResolverRunner({
283 options,
284 config,
285 });
286 this.assetGraph = assetGraph;
287 }
288
289 run(request: Dependency) {
290 return this.resolverRunner.resolve(request);
291 }
292
293 onComplete(
294 request: Dependency,
295 result: DependencyResult,
296 api: RequestRunnerAPI,
297 ) {
298 let dependency = request;
299 let assetGroup = result;
300 if (!assetGroup) {
301 this.assetGraph.resolveDependency(dependency, null);
302 return;
303 }
304
305 let defer = this.shouldDeferDependency(dependency, assetGroup.sideEffects);
306 dependency.isDeferred = defer;
307
308 let assetGroupNode = nodeFromAssetGroup(assetGroup, defer);
309 let existingAssetGroupNode = this.assetGraph.getNode(assetGroupNode.id);
310 if (existingAssetGroupNode) {
311 // Don't overwrite non-deferred asset groups with deferred ones
312 invariant(existingAssetGroupNode.type === 'asset_group');
313 assetGroupNode.deferred = existingAssetGroupNode.deferred && defer;
314 }
315 this.assetGraph.resolveDependency(dependency, assetGroupNode);
316
317 if (existingAssetGroupNode) {
318 // Node already existed, that asset might have deferred dependencies,
319 // recheck all dependencies of all assets of this asset group
320 this.assetGraph.traverse((node, parent, actions) => {
321 if (node == assetGroupNode) {
322 return;
323 }
324
325 if (node.type === 'dependency' && !node.value.isDeferred) {
326 actions.skipChildren();
327 return;
328 }
329
330 if (node.type == 'asset_group') {
331 invariant(parent && parent.type === 'dependency');
332 if (
333 node.deferred &&
334 !this.shouldDeferDependency(parent.value, node.value.sideEffects)
335 ) {
336 parent.value.isDeferred = false;
337 node.deferred = false;
338 this.assetGraph.markIncomplete(node);
339 }
340
341 actions.skipChildren();
342 }
343
344 return node;
345 }, assetGroupNode);
346 }
347
348 // ? Should this happen if asset is deferred?
349 api.invalidateOnFileDelete(assetGroup.filePath);
350
351 // TODO: invalidate dep path requests that have failed and a file creation may fulfill the request
352 }
353
354 // Defer transforming this dependency if it is marked as weak, there are no side effects,
355 // no re-exported symbols are used by ancestor dependencies and the re-exporting asset isn't
356 // using a wildcard.
357 // This helps with performance building large libraries like `lodash-es`, which re-exports
358 // a huge number of functions since we can avoid even transforming the files that aren't used.
359 shouldDeferDependency(dependency: Dependency, sideEffects: ?boolean) {
360 let defer = false;
361 if (
362 dependency.isWeak &&
363 sideEffects === false &&
364 !dependency.symbols.has('*') &&
365 !dependency.env.isLibrary // TODO (T-232): improve the logic below and remove this.
366 ) {
367 let depNode = this.assetGraph.getNode(dependency.id);
368 invariant(depNode);
369
370 let assets = this.assetGraph.getNodesConnectedTo(depNode);
371 let symbols = invertMap(dependency.symbols);
372 invariant(assets.length === 1);
373 let firstAsset = assets[0];
374 invariant(firstAsset.type === 'asset');
375 let resolvedAsset = firstAsset.value;
376 let deps = this.assetGraph.getIncomingDependencies(resolvedAsset);
377 defer = deps.every(
378 d =>
379 !d.symbols.has('*') &&
380 ![...d.symbols.keys()].some(symbol => {
381 let assetSymbol = resolvedAsset.symbols.get(symbol);
382 return assetSymbol != null && symbols.has(assetSymbol);
383 }),
384 );
385 }
386 return defer;
387 }
388}