Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codesplitting across reexports using symbol data #8393

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 16 additions & 3 deletions packages/core/core/src/AssetGraph.js
Expand Up @@ -30,6 +30,15 @@ import {ContentGraph} from '@parcel/graph';
import {createDependency} from './Dependency';
import {type ProjectPath, fromProjectPathRelative} from './projectPath';

export const assetGraphEdgeTypes = {
null: 1,
// In addition to the null edge, a dependency can be connected to the asset containing the symbols
// that the dependency requested (after reexports were skipped).
redirected: 2,
};

export type AssetGraphEdgeType = $Values<typeof assetGraphEdgeTypes>;

type InitOpts = {|
entries?: Array<ProjectPath>,
targets?: Array<Target>,
Expand All @@ -43,7 +52,7 @@ type AssetGraphOpts = {|
|};

type SerializedAssetGraph = {|
...SerializedContentGraph<AssetGraphNode>,
...SerializedContentGraph<AssetGraphNode, AssetGraphEdgeType>,
hash?: ?string,
symbolPropagationRan: boolean,
|};
Expand All @@ -56,10 +65,11 @@ export function nodeFromDep(dep: Dependency): DependencyNode {
deferred: false,
excluded: false,
usedSymbolsDown: new Set(),
usedSymbolsUp: new Set(),
usedSymbolsUp: new Map(),
usedSymbolsDownDirty: true,
usedSymbolsUpDirtyDown: true,
usedSymbolsUpDirtyUp: true,
symbolTarget: null,
};
}

Expand Down Expand Up @@ -109,7 +119,10 @@ export function nodeFromEntryFile(entry: Entry): EntryFileNode {
};
}

export default class AssetGraph extends ContentGraph<AssetGraphNode> {
export default class AssetGraph extends ContentGraph<
AssetGraphNode,
AssetGraphEdgeType,
> {
onNodeRemoved: ?(nodeId: NodeId) => mixed;
hash: ?string;
envCache: Map<string, Environment>;
Expand Down
159 changes: 100 additions & 59 deletions packages/core/core/src/BundleGraph.js
Expand Up @@ -37,6 +37,7 @@ import {Priority, BundleBehavior, SpecifierType} from './types';
import {getBundleGroupId, getPublicId} from './utils';
import {ISOLATED_ENVS} from './public/Environment';
import {fromProjectPath} from './projectPath';
import {assetGraphEdgeTypes} from './AssetGraph';

export const bundleGraphEdgeTypes = {
// A lack of an edge type indicates to follow the edge while traversing
Expand Down Expand Up @@ -146,7 +147,8 @@ export default class BundleGraph {
assetPublicIds: Set<string> = new Set(),
): BundleGraph {
let graph = new ContentGraph<BundleGraphNode, BundleGraphEdgeType>();
let assetGroupIds = new Set();
let assetGroupIds = new Map();
let dependencyIds = new Set();
let assetGraphNodeIdToBundleGraphNodeId = new Map<NodeId, NodeId>();

let assetGraphRootNode =
Expand All @@ -155,63 +157,80 @@ export default class BundleGraph {
: null;
invariant(assetGraphRootNode != null && assetGraphRootNode.type === 'root');

for (let [nodeId, node] of assetGraph.nodes) {
if (node.type === 'asset') {
let {id: assetId} = node.value;
// Generate a new, short public id for this asset to use.
// If one already exists, use it.
let publicId = publicIdByAssetId.get(assetId);
if (publicId == null) {
publicId = getPublicId(assetId, existing =>
assetPublicIds.has(existing),
);
publicIdByAssetId.set(assetId, publicId);
assetPublicIds.add(publicId);
assetGraph.dfs({
visit: nodeId => {
let node = nullthrows(assetGraph.getNode(nodeId));
if (node.type === 'asset') {
let {id: assetId} = node.value;
// Generate a new, short public id for this asset to use.
// If one already exists, use it.
let publicId = publicIdByAssetId.get(assetId);
if (publicId == null) {
publicId = getPublicId(assetId, existing =>
assetPublicIds.has(existing),
);
publicIdByAssetId.set(assetId, publicId);
assetPublicIds.add(publicId);
}
}
}

// Don't copy over asset groups into the bundle graph.
if (node.type === 'asset_group') {
assetGroupIds.add(nodeId);
} else {
let bundleGraphNodeId = graph.addNodeByContentKey(node.id, node);
if (node.id === assetGraphRootNode?.id) {
graph.setRootNodeId(bundleGraphNodeId);
// Don't copy over asset groups into the bundle graph.
if (node.type === 'asset_group') {
assetGroupIds.set(
nodeId,
assetGraph.getNodeIdsConnectedFrom(
nodeId,
assetGraphEdgeTypes.null,
),
);
} else {
if (node.type === 'dependency') {
dependencyIds.add(nodeId);
}
let bundleGraphNodeId = graph.addNodeByContentKey(node.id, node);
if (node.id === assetGraphRootNode?.id) {
graph.setRootNodeId(bundleGraphNodeId);
}
assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId);
}
assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId);
}
}
},
startNodeId: null,
getChildren: nodeId => {
let children = assetGraph.getNodeIdsConnectedFrom(
nodeId,
assetGraphEdgeTypes.redirected,
);
if (children.length > 0) {
return children;
} else {
return assetGraph.getNodeIdsConnectedFrom(
nodeId,
assetGraphEdgeTypes.null,
);
}
},
});

for (let edge of assetGraph.getAllEdges()) {
let fromIds;
if (assetGroupIds.has(edge.from)) {
fromIds = [
...assetGraph.getNodeIdsConnectedTo(
edge.from,
bundleGraphEdgeTypes.null,
),
];
} else {
fromIds = [edge.from];
if (
dependencyIds.has(edge.from) &&
edge.type === assetGraphEdgeTypes.null &&
assetGraph.adjacencyList
.getOutboundEdgesByType(edge.from)
.some(n => n.type === assetGraphEdgeTypes.redirected)
) {
// If there's a redirect edge for a dependency, ignore the normal edge and convert the
// redirect edge into a regular bundlegraph edge.
continue;
}

for (let from of fromIds) {
if (assetGroupIds.has(edge.to)) {
for (let to of assetGraph.getNodeIdsConnectedFrom(
edge.to,
bundleGraphEdgeTypes.null,
)) {
graph.addEdge(
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(from)),
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(to)),
);
}
} else {
graph.addEdge(
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(from)),
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(edge.to)),
);
}
let fromBundleGraph = assetGraphNodeIdToBundleGraphNodeId.get(edge.from);
if (fromBundleGraph == null) continue; // an asset group
for (let to of assetGroupIds.get(edge.to) ?? [edge.to]) {
graph.addEdge(
fromBundleGraph,
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(to)),
);
}
}

Expand Down Expand Up @@ -858,6 +877,17 @@ export default class BundleGraph {
});
}

getDependenciesWithSymbolTarget(
asset: Asset,
): Array<[Dependency, ?Map<Symbol, Symbol>]> {
let nodeId = this._graph.getNodeIdByContentKey(asset.id);
return this._graph.getNodeIdsConnectedFrom(nodeId).map(id => {
let node = nullthrows(this._graph.getNode(id));
invariant(node.type === 'dependency');
return [node.value, node.symbolTarget];
});
}

traverseAssets<TContext>(
bundle: Bundle,
visit: GraphVisitor<Asset, TContext>,
Expand Down Expand Up @@ -1402,9 +1432,9 @@ export default class BundleGraph {
let found = false;
let nonStaticDependency = false;
let skipped = false;
let deps = this.getDependencies(asset).reverse();
let deps = this.getDependenciesWithSymbolTarget(asset).reverse();
let potentialResults = [];
for (let dep of deps) {
for (let [dep, symbolTarget] of deps) {
let depSymbols = dep.symbols;
if (!depSymbols) {
nonStaticDependency = true;
Expand All @@ -1414,7 +1444,10 @@ export default class BundleGraph {
let symbolLookup = new Map(
[...depSymbols].map(([key, val]) => [val.local, key]),
);
let depSymbol = symbolLookup.get(identifier);
let depSymbol =
identifier != null
? symbolLookup.get(symbolTarget?.get(identifier) ?? identifier)
: undefined;
if (depSymbol != null) {
let resolved = this.getResolvedAsset(dep);
if (!resolved || resolved.id === asset.id) {
Expand Down Expand Up @@ -1737,16 +1770,23 @@ export default class BundleGraph {
let node = this._graph.getNodeByContentKey(asset.id);
invariant(node && node.type === 'asset');
return this._symbolPropagationRan
? makeReadOnlySet(node.usedSymbols)
? makeReadOnlySet(new Set(node.usedSymbols.keys()))
: null;
}

getUsedSymbolsDependency(dep: Dependency): ?$ReadOnlySet<Symbol> {
let node = this._graph.getNodeByContentKey(dep.id);
invariant(node && node.type === 'dependency');
return this._symbolPropagationRan
? makeReadOnlySet(node.usedSymbolsUp)
: null;
let result = new Set(node.usedSymbolsUp.keys());
if (node.symbolTarget) {
for (let [k, v] of node.symbolTarget) {
if (result.has(k)) {
result.delete(k);
result.add(v);
}
}
}
return this._symbolPropagationRan ? makeReadOnlySet(result) : null;
}

merge(other: BundleGraph) {
Expand All @@ -1758,6 +1798,7 @@ export default class BundleGraph {

let existingNode = nullthrows(this._graph.getNode(existingNodeId));
// Merge symbols, recompute dep.exluded based on that

if (existingNode.type === 'asset') {
invariant(otherNode.type === 'asset');
existingNode.usedSymbols = new Set([
Expand All @@ -1770,7 +1811,7 @@ export default class BundleGraph {
...existingNode.usedSymbolsDown,
...otherNode.usedSymbolsDown,
]);
existingNode.usedSymbolsUp = new Set([
existingNode.usedSymbolsUp = new Map([
...existingNode.usedSymbolsUp,
...otherNode.usedSymbolsUp,
]);
Expand Down
37 changes: 32 additions & 5 deletions packages/core/core/src/dumpGraphToGraphViz.js
Expand Up @@ -3,6 +3,8 @@
import type {Asset, BundleBehavior} from '@parcel/types';
import type {Graph} from '@parcel/graph';
import type {AssetGraphNode, BundleGraphNode, Environment} from './types';
import type {AssetGraphEdgeType} from './AssetGraph';
import {assetGraphEdgeTypes} from './AssetGraph';
import {bundleGraphEdgeTypes} from './BundleGraph';
import {requestGraphEdgeTypes} from './RequestTracker';

Expand All @@ -21,11 +23,15 @@ const COLORS = {
};

const TYPE_COLORS = {
// bundle graph
bundle: 'blue',
contains: 'grey',
internal_async: 'orange',
references: 'red',
sibling: 'green',
// asset graph
redirected: 'grey',
// request graph
invalidated_by_create: 'green',
invalidated_by_create_above: 'orange',
invalidate_by_update: 'cyan',
Expand All @@ -34,15 +40,18 @@ const TYPE_COLORS = {

export default async function dumpGraphToGraphViz(
graph:
| Graph<AssetGraphNode>
| Graph<AssetGraphNode, AssetGraphEdgeType>
| Graph<{|
assets: Set<Asset>,
sourceBundles: Set<number>,
bundleBehavior?: ?BundleBehavior,
|}>
| Graph<BundleGraphNode>,
name: string,
edgeTypes?: typeof bundleGraphEdgeTypes | typeof requestGraphEdgeTypes,
edgeTypes?:
| typeof assetGraphEdgeTypes
| typeof bundleGraphEdgeTypes
| typeof requestGraphEdgeTypes,
): Promise<void> {
if (
process.env.PARCEL_BUILD_ENV === 'production' ||
Expand Down Expand Up @@ -80,8 +89,13 @@ export default async function dumpGraphToGraphViz(
if (node.type === 'dependency') {
label += node.value.specifier;
let parts = [];
if (node.value.priority !== Priority.sync)
parts.push(node.value.priority);
if (node.value.priority !== Priority.sync) {
parts.push(
Object.entries(Priority).find(
([, v]) => v === node.value.priority,
)?.[0],
);
}
if (node.value.isOptional) parts.push('optional');
if (node.value.specifierType === SpecifierType.url) parts.push('url');
if (node.hasDeferred) parts.push('deferred');
Expand All @@ -103,12 +117,25 @@ export default async function dumpGraphToGraphViz(
label += '\\nweakSymbols: ' + weakSymbols.join(',');
}
if (node.usedSymbolsUp.size > 0) {
label += '\\nusedSymbolsUp: ' + [...node.usedSymbolsUp].join(',');
label +=
'\\nusedSymbolsUp: ' +
[...node.usedSymbolsUp]
.map(([s, sAsset]) =>
sAsset
? `${s}(${sAsset.asset}.${sAsset.symbol ?? ''})`
: `${s}(external)`,
)
.join(',');
}
if (node.usedSymbolsDown.size > 0) {
label +=
'\\nusedSymbolsDown: ' + [...node.usedSymbolsDown].join(',');
}
if (node.symbolTarget && node.symbolTarget?.size > 0) {
label +=
'\\nsymbolTarget: ' +
[...node.symbolTarget].map(([a, b]) => `${a}:${b}`).join(',');
}
} else {
label += '\\nsymbols: cleared';
}
Expand Down