Skip to content

Commit

Permalink
Fix asset deferring
Browse files Browse the repository at this point in the history
  • Loading branch information
mischnic committed Oct 25, 2019
1 parent 36594cb commit cc75395
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 45 deletions.
51 changes: 12 additions & 39 deletions packages/core/core/src/AssetGraph.js
Expand Up @@ -35,19 +35,20 @@ type SerializedAssetGraph = {|
hash: ?string
|};

const invertMap = <K, V>(map: Map<K, V>): Map<V, K> =>
new Map([...map].map(([key, val]) => [val, key]));

const nodeFromDep = (dep: Dependency): DependencyNode => ({
id: dep.id,
type: 'dependency',
value: dep
});

export const nodeFromAssetGroup = (assetGroup: AssetGroup) => ({
export const nodeFromAssetGroup = (
assetGroup: AssetGroup,
deferred: boolean = false
) => ({
id: md5FromObject(assetGroup),
type: 'asset_group',
value: assetGroup
value: assetGroup,
deferred
});

const nodeFromAsset = (asset: Asset) => ({
Expand Down Expand Up @@ -148,42 +149,14 @@ export default class AssetGraph extends Graph<AssetGraphNode> {
}
}

resolveDependency(dependency: Dependency, assetGroup: AssetGroup | null) {
resolveDependency(
dependency: Dependency,
assetGroupNode: AssetGroupNode | null
) {
let depNode = this.nodes.get(dependency.id);
if (!assetGroup || !depNode) return;

let assetGroupNode = nodeFromAssetGroup(assetGroup);
if (!assetGroupNode || !depNode) return;

// Defer transforming this dependency if it is marked as weak, there are no side effects,
// no re-exported symbols are used by ancestor dependencies and the re-exporting asset isn't
// using a wildcard.
// This helps with performance building large libraries like `lodash-es`, which re-exports
// a huge number of functions since we can avoid even transforming the files that aren't used.
let defer = false;
if (
dependency.isWeak &&
assetGroup.sideEffects === false &&
!dependency.symbols.has('*')
) {
let assets = this.getNodesConnectedTo(depNode);
let symbols = invertMap(dependency.symbols);
let firstAsset = assets[0];
invariant(firstAsset.type === 'asset');
let resolvedAsset = firstAsset.value;
let deps = this.getIncomingDependencies(resolvedAsset);
defer = deps.every(
d =>
!d.symbols.has('*') &&
![...d.symbols.keys()].some(symbol => {
let assetSymbol = resolvedAsset.symbols.get(symbol);
return assetSymbol != null && symbols.has(assetSymbol);
})
);
}

if (!defer) {
this.replaceNodesConnectedTo(depNode, [assetGroupNode]);
}
this.replaceNodesConnectedTo(depNode, [assetGroupNode]);
}

resolveAssetGroup(assetGroup: AssetGroup, assets: Array<Asset>) {
Expand Down
103 changes: 98 additions & 5 deletions packages/core/core/src/AssetGraphBuilder.js
Expand Up @@ -6,23 +6,27 @@ import type {FilePath} from '@parcel/types';
import type {
Asset,
AssetGraphNode,
AssetGroupNode,
AssetRequest,
AssetRequestNode,
DepPathRequestNode,
DependencyNode,
ParcelOptions,
Target
Target,
Dependency
} from './types';

import EventEmitter from 'events';
import {md5FromObject, md5FromString} from '@parcel/utils';

import AssetGraph from './AssetGraph';
import AssetGraph, {nodeFromAssetGroup} from './AssetGraph';
import type ParcelConfig from './ParcelConfig';
import RequestGraph from './RequestGraph';
import {PARCEL_VERSION} from './constants';

import dumpToGraphViz from './dumpGraphToGraphViz';
import path from 'path';
import invariant from 'assert';

type Opts = {|
options: ParcelOptions,
Expand All @@ -34,6 +38,9 @@ type Opts = {|
workerFarm: WorkerFarm
|};

const invertMap = <K, V>(map: Map<K, V>): Map<V, K> =>
new Map([...map].map(([key, val]) => [val, key]));

export default class AssetGraphBuilder extends EventEmitter {
assetGraph: AssetGraph;
requestGraph: RequestGraph;
Expand Down Expand Up @@ -121,7 +128,9 @@ export default class AssetGraphBuilder extends EventEmitter {
this.requestGraph.addDepPathRequest(node.value);
break;
case 'asset_group':
this.requestGraph.addAssetRequest(node.id, node.value);
if (!node.deferred) {
this.requestGraph.addAssetRequest(node.id, node.value);
}
break;
case 'asset': {
let asset = node.value;
Expand Down Expand Up @@ -164,11 +173,95 @@ export default class AssetGraphBuilder extends EventEmitter {
}
}

// Defer transforming this dependency if it is marked as weak, there are no side effects,
// no re-exported symbols are used by ancestor dependencies and the re-exporting asset isn't
// using a wildcard.
// This helps with performance building large libraries like `lodash-es`, which re-exports
// a huge number of functions since we can avoid even transforming the files that aren't used.
shouldDeferDependency(dependency: Dependency, sideEffects: ?boolean) {
let defer = false;
if (
dependency.isWeak &&
sideEffects === false &&
!dependency.symbols.has('*')
) {
let depNode = this.assetGraph.getNode(dependency.id);
invariant(depNode);

let assets = this.assetGraph.getNodesConnectedTo(depNode);
let symbols = invertMap(dependency.symbols);
let firstAsset = assets[0];
invariant(firstAsset.type === 'asset');
let resolvedAsset = firstAsset.value;
let deps = this.assetGraph.getIncomingDependencies(resolvedAsset);
defer = deps.every(
d =>
!d.symbols.has('*') &&
![...d.symbols.keys()].some(symbol => {
let assetSymbol = resolvedAsset.symbols.get(symbol);
return assetSymbol != null && symbols.has(assetSymbol);
})
);
}
return defer;
}

handleCompletedDepPathRequest(
requestNode: DepPathRequestNode,
result: AssetRequest | null
assetGroup: AssetRequest | null
) {
this.assetGraph.resolveDependency(requestNode.value, result);
if (!assetGroup) {
return;
}
let dependency = requestNode.value;

let defer = this.shouldDeferDependency(dependency, assetGroup.sideEffects);

let assetGroupNode = nodeFromAssetGroup(assetGroup, defer);
let assetGroupExisted = this.assetGraph.hasNode(assetGroupNode.id);
this.assetGraph.resolveDependency(dependency, assetGroupNode);
if (assetGroupExisted) {
// Node already exists, that asset might have deferred dependencies,
// (Recheck) all dependencies of all assets of this asset group
let assetNodes = this.assetGraph
.getNodesConnectedFrom(assetGroupNode)
.map(v => {
invariant(v.type === 'asset');
return v;
});
for (let assetNode of assetNodes) {
let dependencyNodes = this.assetGraph
.getNodesConnectedFrom(assetNode)
.map(v => {
invariant(v.type === 'dependency');
return v;
});
for (let depNode of dependencyNodes) {
let assetGroupNodes = this.assetGraph
.getNodesConnectedFrom(depNode)
.map(v => {
invariant(v.type === 'asset_group');
return v;
});
invariant(assetGroupNodes.length === 1);
let assetGroupNode = assetGroupNodes[0];

if (
assetGroupNode.deferred &&
!this.shouldDeferDependency(
depNode.value,
assetGroupNode.value.sideEffects
)
) {
assetGroupNode.deferred = false;
this.requestGraph.addAssetRequest(
assetGroupNode.id,
assetGroupNode.value
);
}
}
}
}
}

respondToFSEvents(events: Array<Event>) {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/core/src/dumpGraphToGraphViz.js
Expand Up @@ -54,6 +54,10 @@ export default async function dumpGraphToGraphViz(
if (node.value.env) label += ` (${getEnvDescription(node.value.env)})`;
} else if (node.type === 'asset') {
label += path.basename(node.value.filePath) + '#' + node.value.type;
} else if (node.type === 'asset_group') {
if (node.deferred) {
label += '(deferred)';
}
} else if (node.type === 'file') {
label += path.basename(node.value.filePath);
} else if (node.type === 'transformer_request') {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/core/src/types.js
Expand Up @@ -157,7 +157,8 @@ export type AssetGroupNode = {|
id: string,
+type: 'asset_group',
// An asset group node is used to
value: AssetGroup
value: AssetGroup,
deferred: boolean
|};

export type DepPathRequestNode = {|
Expand Down

0 comments on commit cc75395

Please sign in to comment.