1 |
|
2 |
|
3 | import type {Dependency} from '@parcel/types';
|
4 | import type {
|
5 | AssetRequestDesc,
|
6 | Bundle as InternalBundle,
|
7 | NodeId,
|
8 | ParcelOptions,
|
9 | } from './types';
|
10 | import type InternalBundleGraph from './BundleGraph';
|
11 | import type AssetGraphBuilder from './AssetGraphBuilder';
|
12 | import type ParcelConfig from './ParcelConfig';
|
13 | import type PluginOptions from './public/PluginOptions';
|
14 |
|
15 | import invariant from 'assert';
|
16 | import nullthrows from 'nullthrows';
|
17 | import AssetGraph, {nodeFromAssetGroup} from './AssetGraph';
|
18 | import BundleGraph from './public/BundleGraph';
|
19 | import {removeAssetGroups} from './BundleGraph';
|
20 | import {NamedBundle} from './public/Bundle';
|
21 | import {setDifference} from '@parcel/utils';
|
22 | import {PluginLogger} from '@parcel/logger';
|
23 | import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic';
|
24 |
|
25 | type RuntimeConnection = {|
|
26 | bundle: InternalBundle,
|
27 | assetRequest: AssetRequestDesc,
|
28 | dependency: ?Dependency,
|
29 | isEntry: ?boolean,
|
30 | |};
|
31 |
|
32 | export default async function applyRuntimes({
|
33 | bundleGraph,
|
34 | config,
|
35 | options,
|
36 | pluginOptions,
|
37 | runtimesBuilder,
|
38 | }: {|
|
39 | bundleGraph: InternalBundleGraph,
|
40 | config: ParcelConfig,
|
41 | options: ParcelOptions,
|
42 | pluginOptions: PluginOptions,
|
43 | runtimesBuilder: AssetGraphBuilder,
|
44 | |}): Promise<void> {
|
45 | let connections: Array<RuntimeConnection> = [];
|
46 |
|
47 | for (let bundle of bundleGraph.getBundles()) {
|
48 | let runtimes = await config.getRuntimes(bundle.env.context);
|
49 | for (let runtime of runtimes) {
|
50 | try {
|
51 | let applied = await runtime.plugin.apply({
|
52 | bundle: new NamedBundle(bundle, bundleGraph, options),
|
53 | bundleGraph: new BundleGraph(bundleGraph, options),
|
54 | options: pluginOptions,
|
55 | logger: new PluginLogger({origin: runtime.name}),
|
56 | });
|
57 |
|
58 | if (applied) {
|
59 | let runtimeAssets = Array.isArray(applied) ? applied : [applied];
|
60 | for (let {code, dependency, filePath, isEntry} of runtimeAssets) {
|
61 | let assetRequest = {
|
62 | code,
|
63 | filePath,
|
64 | env: bundle.env,
|
65 | };
|
66 | connections.push({
|
67 | bundle,
|
68 | assetRequest,
|
69 | dependency: dependency,
|
70 | isEntry,
|
71 | });
|
72 | }
|
73 | }
|
74 | } catch (e) {
|
75 | throw new ThrowableDiagnostic({
|
76 | diagnostic: errorToDiagnostic(e, runtime.name),
|
77 | });
|
78 | }
|
79 | }
|
80 | }
|
81 |
|
82 | let runtimesAssetGraph = await reconcileNewRuntimes(
|
83 | runtimesBuilder,
|
84 | connections,
|
85 | );
|
86 |
|
87 | let runtimesGraph = removeAssetGroups(runtimesAssetGraph);
|
88 |
|
89 |
|
90 |
|
91 | bundleGraph._graph.merge(runtimesGraph);
|
92 |
|
93 | for (let {bundle, assetRequest, dependency, isEntry} of connections) {
|
94 | let assetGroupNode = nodeFromAssetGroup(assetRequest);
|
95 | let assetGroupAssets = runtimesAssetGraph.getNodesConnectedFrom(
|
96 | assetGroupNode,
|
97 | );
|
98 | invariant(assetGroupAssets.length === 1);
|
99 | let runtimeNode = assetGroupAssets[0];
|
100 | invariant(runtimeNode.type === 'asset');
|
101 |
|
102 | let duplicatedAssetIds: Set<NodeId> = new Set();
|
103 | runtimesGraph.traverse((node, _, actions) => {
|
104 | if (node.type !== 'dependency') {
|
105 | return;
|
106 | }
|
107 |
|
108 | let assets = runtimesGraph.getNodesConnectedFrom(node).map(assetNode => {
|
109 | invariant(assetNode.type === 'asset');
|
110 | return assetNode.value;
|
111 | });
|
112 |
|
113 | for (let asset of assets) {
|
114 | if (bundleGraph.isAssetInAncestorBundles(bundle, asset)) {
|
115 | duplicatedAssetIds.add(asset.id);
|
116 | actions.skipChildren();
|
117 | }
|
118 | }
|
119 | }, runtimeNode);
|
120 |
|
121 | runtimesGraph.traverse((node, _, actions) => {
|
122 | if (node.type === 'asset' || node.type === 'dependency') {
|
123 | if (duplicatedAssetIds.has(node.id)) {
|
124 | actions.skipChildren();
|
125 | return;
|
126 | }
|
127 |
|
128 | bundleGraph._graph.addEdge(bundle.id, node.id, 'contains');
|
129 | }
|
130 | }, runtimeNode);
|
131 |
|
132 | bundleGraph._graph.addEdge(
|
133 | dependency
|
134 | ? dependency.id
|
135 | : nullthrows(bundleGraph._graph.getNode(bundle.id)).id,
|
136 | runtimeNode.id,
|
137 | );
|
138 |
|
139 | if (isEntry) {
|
140 | bundle.entryAssetIds.unshift(runtimeNode.id);
|
141 | }
|
142 | }
|
143 | }
|
144 |
|
145 | async function reconcileNewRuntimes(
|
146 | runtimesBuilder: AssetGraphBuilder,
|
147 | connections: Array<RuntimeConnection>,
|
148 | ): Promise<AssetGraph> {
|
149 | let {assetGraph} = runtimesBuilder;
|
150 |
|
151 | let assetRequestNodesById = new Map(
|
152 | connections
|
153 | .map(t => t.assetRequest)
|
154 | .map(request => {
|
155 | let node = nodeFromAssetGroup(request);
|
156 | return [node.id, node];
|
157 | }),
|
158 | );
|
159 | let newRequestIds = new Set(assetRequestNodesById.keys());
|
160 | let oldRequestIds = new Set(
|
161 | assetGraph.getEntryAssetGroupNodes().map(node => node.id),
|
162 | );
|
163 |
|
164 | let toAdd = setDifference(newRequestIds, oldRequestIds);
|
165 | let toRemove = setDifference(oldRequestIds, newRequestIds);
|
166 |
|
167 | assetGraph.replaceNodesConnectedTo(
|
168 | nullthrows(assetGraph.getRootNode()),
|
169 | [...toAdd].map(requestId =>
|
170 | nullthrows(assetRequestNodesById.get(requestId)),
|
171 | ),
|
172 | node => toRemove.has(node.id),
|
173 | );
|
174 |
|
175 |
|
176 | return (await runtimesBuilder.build()).assetGraph;
|
177 | }
|