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

Propagate symbols data in bundler #4375

Closed
wants to merge 2 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 136 additions & 1 deletion packages/bundlers/default/src/DefaultBundler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow strict-local

import type {Asset, Bundle, MutableBundleGraph} from '@parcel/types';
import type {Asset, Bundle, MutableBundleGraph, Symbol} from '@parcel/types';

import invariant from 'assert';
import {Bundler} from '@parcel/plugin';
Expand Down Expand Up @@ -270,6 +270,53 @@ export default new Bundler({
deduplicateBundle(bundleGraph, sharedBundle);
}
},
propagate({bundleGraph}) {
// TODO put this in a separate plugin

let usedExports: Map<string, Set<Symbol>> = new Map();
let processedDeps = new Set<string>();
bundleGraph.traverseBundles({
enter(bundle) {
setUsedExports(usedExports, processedDeps, bundle, bundleGraph);
},
exit(bundle) {
bundle.traverse((node, shouldWrap) => {
switch (node.type) {
case 'dependency':
// Mark assets that should be wrapped, based on metadata in the incoming dependency tree
if (shouldWrap || Boolean(node.value.meta.shouldWrap)) {
let resolved = bundleGraph.getDependencyResolution(
node.value,
bundle,
);
if (resolved) {
resolved.meta.shouldWrap = true;
}
return true;
}
break;
case 'asset': {
let assetUsedExports = usedExports.get(node.value.id);
if (assetUsedExports) {
let wildcardUsed = assetUsedExports.has('*');
if (wildcardUsed) {
let assetExports = node.value.meta.exportsIdentifier;
invariant(typeof assetExports === 'string');
node.value.exportedSymbols.set('*', assetExports);
}
for (let [symbol, id] of node.value.symbols) {
if (assetUsedExports.has(symbol)) {
node.value.exportedSymbols.set(symbol, id);
}
}
}
break;
}
}
});
},
});
},
});

function deduplicateBundle(bundleGraph: MutableBundleGraph, bundle: Bundle) {
Expand Down Expand Up @@ -297,3 +344,91 @@ function deduplicateBundle(bundleGraph: MutableBundleGraph, bundle: Bundle) {
}
});
}

// ----------------------------------------------------------

function setUsedExports(
usedExports,
processedDeps,
bundle,
bundleGraph,
): Map<string, Set<Symbol>> {
bundle.traverseAssets(asset => {
// TODO Entry assets should export eveything?!
for (let dep of bundleGraph.getIncomingDependencies(asset)) {
if (processedDeps.has(dep.id)) continue;
if (dep.isEntry) {
processedDeps.add(dep.id);
for (let {asset: symbolAsset, exportSymbol} of bundleGraph.getSymbols(
asset,
)) {
if (exportSymbol != null) {
markUsed(symbolAsset, exportSymbol);
markUsedEntry(asset, exportSymbol); // mark in the asset itself because it is an entry
}
}
}
}

for (let dep of bundleGraph.getDependencies(asset)) {
if (processedDeps.has(dep.id)) continue;
processedDeps.add(dep.id);

let resolvedAsset = bundleGraph.getDependencyResolution(dep, bundle);
if (!resolvedAsset) {
continue;
}
for (let [symbol, identifier] of dep.symbols) {
if (identifier === '*') {
continue;
}

if (symbol === '*') {
for (let {asset: symbolAsset, exportSymbol} of bundleGraph.getSymbols(
resolvedAsset,
)) {
if (exportSymbol != null) {
markUsed(symbolAsset, exportSymbol);
}
}
}

markUsed(resolvedAsset, symbol);
}
}

// // If the asset is referenced by another bundle, include all exports.
// if (bundleGraph.isAssetReferencedByAnotherBundleOfType(asset, 'js')) {
// markUsed(asset, '*');
// for (let {asset: a, symbol} of bundleGraph.getExportedSymbols(asset)) {
// if (symbol != null) {
// markUsed(a, symbol);
// }
// }
// }
});

// TODO merge these two functions
function markUsedEntry(asset, symbol) {
let used = usedExports.get(asset.id);
if (!used) {
used = new Set();
usedExports.set(asset.id, used);
}

used.add(symbol);
}

function markUsed(asset, symbol) {
let resolved = bundleGraph.resolveSymbol(asset, symbol);
let used = usedExports.get(resolved.asset.id);
if (!used) {
used = new Set();
usedExports.set(resolved.asset.id, used);
}

used.add(resolved.exportSymbol);
}

return usedExports;
}
55 changes: 46 additions & 9 deletions packages/core/core/src/BundleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ type BundleGraphEdgeTypes =
// Using this type prevents referenced assets from being traversed normally.
| 'references';

type ResolvedSymbol = {|
asset: Asset,
exportSymbol: Symbol,
symbol: ?Symbol,
|};

export default class BundleGraph {
// TODO: These hashes are being invalidated in mutative methods, but this._graph is not a private
// property so it is possible to reach in and mutate the graph without invalidating these hashes.
Expand Down Expand Up @@ -178,13 +184,13 @@ export default class BundleGraph {

getDependencyAssets(dependency: Dependency): Array<Asset> {
let dependencyNode = nullthrows(this._graph.getNode(dependency.id));
return this._graph
.getNodesConnectedFrom(dependencyNode)
.filter(node => node.type === 'asset')
.map(node => {
invariant(node.type === 'asset');
return node.value;
});
let assets = [];
this.traverseContents((node, _, traverse) => {
if (node.type === 'dependency') {
if (node.id !== dependency.id) traverse.skipChildren();
} else assets.push(node.value);
}, dependencyNode);
return assets;
}

getDependencyResolution(dep: Dependency, bundle?: Bundle): ?Asset {
Expand All @@ -204,6 +210,7 @@ export default class BundleGraph {
firstAsset;

// If a resolution still hasn't been found, return the first referenced asset.
// TODO still will never work?
if (resolved == null) {
this._graph.traverse(
(node, _, traversal) => {
Expand Down Expand Up @@ -401,11 +408,13 @@ export default class BundleGraph {

traverseContents<TContext>(
visit: GraphVisitor<AssetNode | DependencyNode, TContext>,
startNode?: BundleGraphNode,
): ?TContext {
return this._graph.filteredTraverse(
node =>
node.type === 'asset' || node.type === 'dependency' ? node : null,
visit,
startNode,
);
}

Expand Down Expand Up @@ -527,7 +536,7 @@ export default class BundleGraph {
);
}

resolveSymbol(asset: Asset, symbol: Symbol) {
resolveSymbol(asset: Asset, symbol: Symbol): ResolvedSymbol {
let identifier = asset.symbols.get(symbol);
if (symbol === '*') {
return {asset, exportSymbol: '*', symbol: identifier};
Expand Down Expand Up @@ -584,13 +593,41 @@ export default class BundleGraph {
return {asset, exportSymbol: symbol, symbol: identifier};
}

getExportedSymbols(asset: Asset) {
// all symbols that this asset is (in)directly exporting
getSymbols(asset: Asset): Array<ResolvedSymbol> {
let symbols = [];

for (let symbol of asset.symbols.keys()) {
symbols.push(this.resolveSymbol(asset, symbol));
}

let deps = this.getDependencies(asset);
for (let dep of deps) {
if (dep.symbols.get('*') === '*') {
let resolved = nullthrows(this.getDependencyResolution(dep));
let exported = this.getSymbols(resolved).filter(
s => s.exportSymbol !== 'default',
);
symbols.push(...exported);
}
}

return symbols;
}

// the used symbols that this asset is exporting
getExportedSymbols(asset: Asset): Array<ResolvedSymbol> {
let symbols = [];

for (let [exportSymbol, symbol] of asset.exportedSymbols) {
//TODO ?
if (exportSymbol === '*') {
symbols.push({asset: asset, exportSymbol, symbol});
} else {
symbols.push(this.resolveSymbol(asset, exportSymbol));
}
}

let deps = this.getDependencies(asset);
for (let dep of deps) {
if (dep.symbols.get('*') === '*') {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/core/src/BundlerRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ export default class BundlerRunner {
assertSignalNotAborted(signal);
await dumpGraphToGraphViz(bundleGraph, 'after_runtimes');

try {
await bundler.propagate({
bundleGraph: new BundleGraph(internalBundleGraph, this.options),
options: this.pluginOptions,
logger: new PluginLogger({origin: this.config.getBundlerName()}),
});
} catch (e) {
throw new ThrowableDiagnostic({
diagnostic: errorToDiagnostic(e, this.config.getBundlerName()),
});
}

if (cacheKey != null) {
await this.options.cache.set(cacheKey, internalBundleGraph);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/core/src/InternalAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type AssetOptions = {|
pipeline?: ?string,
stats: Stats,
symbols?: Map<Symbol, Symbol>,
exportedSymbols?: Map<Symbol, Symbol>,
sideEffects?: boolean,
uniqueKey?: ?string,
plugin?: PackageName,
Expand Down Expand Up @@ -96,6 +97,7 @@ export function createAsset(options: AssetOptions): Asset {
meta: options.meta || {},
stats: options.stats,
symbols: options.symbols || new Map(),
exportedSymbols: options.exportedSymbols || new Map(),
sideEffects: options.sideEffects != null ? options.sideEffects : true,
uniqueKey: uniqueKey,
plugin: options.plugin,
Expand Down
Loading