1 |
|
2 |
|
3 | import type {FilePath} from '@parcel/types';
|
4 | import type WorkerFarm from '@parcel/workers';
|
5 | import type {
|
6 | AssetRequestDesc,
|
7 | AssetRequestResult,
|
8 | Dependency,
|
9 | Entry,
|
10 | ParcelOptions,
|
11 | TransformationOpts,
|
12 | } from './types';
|
13 | import type RequestTracker, {RequestRunnerAPI} from './RequestTracker';
|
14 | import type AssetGraph from './AssetGraph';
|
15 | import type ParcelConfig from './ParcelConfig';
|
16 | import type {TargetResolveResult} from './TargetResolver';
|
17 | import type {EntryResult} from './EntryResolver';
|
18 |
|
19 | import invariant from 'assert';
|
20 | import nullthrows from 'nullthrows';
|
21 | import {isGlob} from '@parcel/utils';
|
22 | import {nodeFromAssetGroup} from './AssetGraph';
|
23 | import ResolverRunner from './ResolverRunner';
|
24 | import {EntryResolver} from './EntryResolver';
|
25 | import TargetResolver from './TargetResolver';
|
26 | import {RequestRunner, generateRequestId} from './RequestTracker';
|
27 |
|
28 | export type AssetGraphBuildRequest =
|
29 | | EntryRequest
|
30 | | TargetRequest
|
31 | | AssetRequest
|
32 | | DepPathRequest;
|
33 |
|
34 | type EntryRequest = {|
|
35 | id: string,
|
36 | +type: 'entry_request',
|
37 | request: FilePath,
|
38 | result?: EntryResult,
|
39 | |};
|
40 |
|
41 | type TargetRequest = {|
|
42 | id: string,
|
43 | +type: 'target_request',
|
44 | request: Entry,
|
45 | result?: TargetResolveResult,
|
46 | |};
|
47 |
|
48 | type AssetRequest = {|
|
49 | id: string,
|
50 | +type: 'asset_request',
|
51 | request: AssetRequestDesc,
|
52 | result?: AssetRequestResult,
|
53 | |};
|
54 |
|
55 | type DependencyResult = AssetRequestDesc | null | void;
|
56 | type DepPathRequest = {|
|
57 | id: string,
|
58 | +type: 'dep_path_request',
|
59 | request: Dependency,
|
60 | result?: DependencyResult,
|
61 | |};
|
62 |
|
63 | export 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 |
|
86 |
|
87 | for (let file of result.files) {
|
88 | api.invalidateOnFileUpdate(file.filePath);
|
89 | }
|
90 |
|
91 |
|
92 |
|
93 | if (isGlob(request)) {
|
94 | api.invalidateOnFileCreate(request);
|
95 | }
|
96 | }
|
97 | }
|
98 |
|
99 | export 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 |
|
129 |
|
130 | for (let file of result.files) {
|
131 | api.invalidateOnFileUpdate(file.filePath);
|
132 | }
|
133 | }
|
134 | }
|
135 |
|
136 | export 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 |
|
191 | let graph = this.tracker.graph;
|
192 | let subrequestNodes = [];
|
193 |
|
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 |
|
228 | for (let [moduleSpecifier, version] of result.devDeps) {
|
229 | let depVersionRequst = {
|
230 | moduleSpecifier,
|
231 | resolveFrom: result.resolvedPath,
|
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 |
|
260 | }
|
261 | }
|
262 |
|
263 | const invertMap = <K, V>(map: Map<K, V>): Map<V, K> =>
|
264 | new Map([...map].map(([key, val]) => [val, key]));
|
265 |
|
266 | export 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 | }
|