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

Code splitting across reexports using symbol data by splitting dependencies #8432

Merged
merged 49 commits into from Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
44f192a
POC
mischnic Aug 11, 2022
3021160
f
mischnic Aug 11, 2022
f1f0a5d
revert usedSymbolsDown to Set
mischnic Aug 11, 2022
7c04c82
Globally renamed symbols
mischnic Aug 11, 2022
f389a7f
Fixup
mischnic Aug 11, 2022
8bb75b5
Fixup
mischnic Aug 11, 2022
f62b1f4
Stringify dep priority
mischnic Aug 11, 2022
3af446d
process.env.PARCEL_SYMBOLS_CODESPLIT
mischnic Aug 11, 2022
cdad7c1
Respect side effects when forwarding
mischnic Aug 11, 2022
9abdd07
Externals
mischnic Aug 11, 2022
0bbc8de
Use current asset for ns import
mischnic Aug 11, 2022
a198221
Remove unnecessarily strict assertions
mischnic Aug 11, 2022
cacb9cf
Edge types for asset graph
mischnic Aug 11, 2022
83b223e
Remove getDependencySymbolTarget
mischnic Aug 12, 2022
ca4febe
Nullable symbolTarget
mischnic Aug 12, 2022
523d04e
Also remove redirected edges again
mischnic Aug 12, 2022
97dc341
Prefer redirected edges for bundle graph
mischnic Aug 12, 2022
01e5cef
Dump symbolTarget
mischnic Aug 12, 2022
63efc43
Add test
mischnic Aug 12, 2022
30d1361
Fixup assertions
mischnic Aug 17, 2022
89e3265
Disable for async imports for now
mischnic Aug 17, 2022
298bdb9
Fixup
mischnic Aug 17, 2022
acd949f
Fixup
mischnic Aug 17, 2022
3a7fdf9
Fixup assertions
mischnic Aug 17, 2022
9ee9ecf
Fixup
mischnic Aug 17, 2022
0d00ab8
WIP
mischnic Aug 25, 2022
13a2cc1
f
mischnic Aug 25, 2022
eb67f69
f
mischnic Aug 25, 2022
d013aa3
f
mischnic Aug 29, 2022
18fc33f
Fixup for externals
mischnic Aug 29, 2022
b2a407d
Remove replacement part of propagation
mischnic Aug 29, 2022
5aaccc1
f
mischnic Aug 29, 2022
f9baf0c
f
mischnic Aug 29, 2022
2d1590f
f
mischnic Aug 29, 2022
e9aedcf
fixup tests
mischnic Aug 29, 2022
91b3567
Fixups
mischnic Aug 29, 2022
64dc9a7
Cleanup
mischnic Aug 29, 2022
533713d
Add test
mischnic Aug 31, 2022
9698d0d
Cleanup
mischnic Sep 1, 2022
0a4c8b3
Less strict test
mischnic Sep 1, 2022
e7deed2
Cleanup
mischnic Sep 1, 2022
8beca81
TODO comment
mischnic Sep 1, 2022
8397d4b
Remove symbolTarget
mischnic Sep 1, 2022
9da483a
Remove bundleGraph.getSymbols
mischnic Sep 1, 2022
ad8d53f
usedSymbolsUp: ambiguous
mischnic Sep 1, 2022
c2895b1
Remove unused DependencySymbols
mischnic Sep 1, 2022
af3e716
Cleanup
mischnic Sep 1, 2022
c1e5ce0
Fixup SW runtime
mischnic Sep 1, 2022
c2eb487
Merge branch 'v2' into symbols-codesplit-split-core
devongovett Sep 7, 2022
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
2 changes: 1 addition & 1 deletion packages/core/core/src/AssetGraph.js
Expand Up @@ -56,7 +56,7 @@ 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,
Expand Down
193 changes: 160 additions & 33 deletions packages/core/core/src/BundleGraph.js
Expand Up @@ -7,6 +7,7 @@ import type {
TraversalActions,
} from '@parcel/types';
import type {
ContentKey,
ContentGraphOpts,
NodeId,
SerializedContentGraph,
Expand All @@ -31,7 +32,7 @@ import invariant from 'assert';
import nullthrows from 'nullthrows';
import {ContentGraph, ALL_EDGE_TYPES, mapVisitor} from '@parcel/graph';
import {Hash, hashString} from '@parcel/hash';
import {objectSortedEntriesDeep, getRootDir} from '@parcel/utils';
import {DefaultMap, objectSortedEntriesDeep, getRootDir} from '@parcel/utils';

import {Priority, BundleBehavior, SpecifierType} from './types';
import {getBundleGroupId, getPublicId} from './utils';
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 dependencies = new Map();
let assetGraphNodeIdToBundleGraphNodeId = new Map<NodeId, NodeId>();

let assetGraphRootNode =
Expand All @@ -168,50 +170,175 @@ export default class BundleGraph {
publicIdByAssetId.set(assetId, publicId);
assetPublicIds.add(publicId);
}
} else if (node.type === 'asset_group') {
assetGroupIds.set(nodeId, assetGraph.getNodeIdsConnectedFrom(nodeId));
}
}

let walkVisited = new Set();
function walk(nodeId) {
if (walkVisited.has(nodeId)) return;
walkVisited.add(nodeId);

let node = nullthrows(assetGraph.getNode(nodeId));
if (node.type === 'dependency' && node.value.symbols != null) {
// asset -> symbols that should be imported directly from that asset
let targets = new DefaultMap<ContentKey, Map<Symbol, Symbol>>(
() => new Map(),
);
let externalSymbols = new Set();
let hasAmbiguousSymbols = false;

for (let [symbol, resolvedSymbol] of node.usedSymbolsUp) {
if (resolvedSymbol) {
targets
.get(resolvedSymbol.asset)
.set(symbol, resolvedSymbol.symbol ?? symbol);
} else if (resolvedSymbol === null) {
externalSymbols.add(symbol);
} else if (resolvedSymbol === undefined) {
hasAmbiguousSymbols = true;
break;
}
}

if (
// Only perform rewriting when there is an imported symbol
// - If the target is side-effect-free, the symbols point to the actual target and removing
// the original dependency resolution is fine
// - Otherwise, keep this dependency unchanged for its potential side effects
node.usedSymbolsUp.size > 0 &&
// Only perform rewriting if the dependency only points to a single asset (e.g. CSS modules)
!hasAmbiguousSymbols &&
// TODO We currently can't rename imports in async imports, e.g. from
// (parcelRequire("...")).then(({ a }) => a);
// to
// (parcelRequire("...")).then(({ a: b }) => a);
// or
// (parcelRequire("...")).then((a)=>a);
// if the reexporting asset did `export {a as b}` or `export * as a`
node.value.priority === Priority.sync
) {
// TODO adjust sourceAssetIdNode.value.dependencies ?
let deps = [
// Keep the original dependency
{
asset: null,
dep: graph.addNodeByContentKey(node.id, {
...node,
value: {
...node.value,
symbols: node.value.symbols
? new Map(
[...node.value.symbols].filter(([k]) =>
externalSymbols.has(k),
),
)
: undefined,
},
usedSymbolsUp: new Map(
[...node.usedSymbolsUp].filter(([k]) =>
externalSymbols.has(k),
),
),
usedSymbolsDown: new Set(),
excluded: externalSymbols.size === 0,
}),
},
...[...targets].map(([asset, target]) => {
let newNodeId = hashString(
node.id + [...target.keys()].join(','),
);
return {
asset,
dep: graph.addNodeByContentKey(newNodeId, {
...node,
id: newNodeId,
value: {
...node.value,
id: newNodeId,
symbols: node.value.symbols
? new Map(
[...node.value.symbols]
.filter(([k]) => target.has(k) || k === '*')
.map(([k, v]) => [target.get(k) ?? k, v]),
)
: undefined,
},
usedSymbolsUp: new Map(
[...node.usedSymbolsUp]
.filter(([k]) => target.has(k) || k === '*')
.map(([k, v]) => [target.get(k) ?? k, v]),
),
usedSymbolsDown: new Set(),
}),
};
}),
];
dependencies.set(nodeId, deps);

// Jump to the dependencies that are used in this dependency
for (let id of targets.keys()) {
walk(assetGraph.getNodeIdByContentKey(id));
}
return;
} else {
// No special handling
let bundleGraphNodeId = graph.addNodeByContentKey(node.id, node);
assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId);
}
}
// Don't copy over asset groups into the bundle graph.
if (node.type === 'asset_group') {
assetGroupIds.add(nodeId);
} else {
else if (node.type !== 'asset_group') {
let bundleGraphNodeId = graph.addNodeByContentKey(node.id, node);
if (node.id === assetGraphRootNode?.id) {
graph.setRootNodeId(bundleGraphNodeId);
}
assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId);
}

for (let id of assetGraph.getNodeIdsConnectedFrom(nodeId)) {
walk(id);
}
}
walk(nullthrows(assetGraph.rootNodeId));

for (let edge of assetGraph.getAllEdges()) {
let fromIds;
if (assetGroupIds.has(edge.from)) {
fromIds = [
...assetGraph.getNodeIdsConnectedTo(
edge.from,
bundleGraphEdgeTypes.null,
),
];
} else {
fromIds = [edge.from];
continue;
}

for (let from of fromIds) {
if (assetGroupIds.has(edge.to)) {
for (let to of assetGraph.getNodeIdsConnectedFrom(
edge.to,
bundleGraphEdgeTypes.null,
)) {
if (dependencies.has(edge.from)) {
// Discard previous edge, insert outgoing edges for all split dependencies
for (let {asset, dep} of nullthrows(dependencies.get(edge.from))) {
if (asset != null) {
graph.addEdge(
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(from)),
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(to)),
dep,
nullthrows(
assetGraphNodeIdToBundleGraphNodeId.get(
assetGraph.getNodeIdByContentKey(asset),
),
),
);
}
} else {
graph.addEdge(
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(from)),
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(edge.to)),
);
}
continue;
}
if (!assetGraphNodeIdToBundleGraphNodeId.has(edge.from)) {
continue;
}

let to: Array<NodeId> = dependencies.get(edge.to)?.map(v => v.dep) ??
assetGroupIds
.get(edge.to)
?.map(id =>
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(id)),
) ?? [nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(edge.to))];

for (let t of to) {
graph.addEdge(
nullthrows(assetGraphNodeIdToBundleGraphNodeId.get(edge.from)),
t,
);
}
}

Expand Down Expand Up @@ -1737,16 +1864,15 @@ 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());
return this._symbolPropagationRan ? makeReadOnlySet(result) : null;
}

merge(other: BundleGraph) {
Expand All @@ -1758,6 +1884,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 +1897,7 @@ export default class BundleGraph {
...existingNode.usedSymbolsDown,
...otherNode.usedSymbolsDown,
]);
existingNode.usedSymbolsUp = new Set([
existingNode.usedSymbolsUp = new Map([
...existingNode.usedSymbolsUp,
...otherNode.usedSymbolsUp,
]);
Expand Down
24 changes: 21 additions & 3 deletions packages/core/core/src/dumpGraphToGraphViz.js
Expand Up @@ -21,11 +21,14 @@ const COLORS = {
};

const TYPE_COLORS = {
// bundle graph
bundle: 'blue',
contains: 'grey',
internal_async: 'orange',
references: 'red',
sibling: 'green',
// asset graph
// request graph
invalidated_by_create: 'green',
invalidated_by_create_above: 'orange',
invalidate_by_update: 'cyan',
Expand Down Expand Up @@ -80,8 +83,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,7 +111,17 @@ 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 ?? ''})`
: sAsset === null
? `${s}(external)`
: `${s}(ambiguous)`,
)
.join(',');
}
if (node.usedSymbolsDown.size > 0) {
label +=
Expand Down
1 change: 0 additions & 1 deletion packages/core/core/src/public/Symbols.js
Expand Up @@ -27,7 +27,6 @@ const EMPTY_ITERATOR = {
const inspect = Symbol.for('nodejs.util.inspect.custom');

let valueToSymbols: WeakMap<Asset, AssetSymbols> = new WeakMap();

export class AssetSymbols implements IAssetSymbols {
/*::
@@iterator(): Iterator<[ISymbol, {|local: ISymbol, loc: ?SourceLocation, meta?: ?Meta|}]> { return ({}: any); }
Expand Down