@parcel/core
Version:
1,058 lines (1,030 loc) • 66.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.bundleGraphEdgeTypes = void 0;
function _assert() {
const data = _interopRequireDefault(require("assert"));
_assert = function () {
return data;
};
return data;
}
function _nullthrows() {
const data = _interopRequireDefault(require("nullthrows"));
_nullthrows = function () {
return data;
};
return data;
}
function _graph() {
const data = require("@parcel/graph");
_graph = function () {
return data;
};
return data;
}
function _rust() {
const data = require("@parcel/rust");
_rust = function () {
return data;
};
return data;
}
function _utils() {
const data = require("@parcel/utils");
_utils = function () {
return data;
};
return data;
}
var _types = require("./types");
var _utils2 = require("./utils");
var _Environment = require("./public/Environment");
var _projectPath = require("./projectPath");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const bundleGraphEdgeTypes = exports.bundleGraphEdgeTypes = {
// A lack of an edge type indicates to follow the edge while traversing
// the bundle's contents, e.g. `bundle.traverse()` during packaging.
null: 1,
// Used for constant-time checks of presence of a dependency or asset in a bundle,
// avoiding bundle traversal in cases like `isAssetInAncestors`
contains: 2,
// Connections between bundles and bundle groups, for quick traversal of the
// bundle hierarchy.
bundle: 3,
// When dependency -> asset: Indicates that the asset a dependency references
// is contained in another bundle.
// When dependency -> bundle: Indicates the bundle is necessary for any bundles
// with the dependency.
// When bundle -> bundle: Indicates the target bundle is necessary for the
// source bundle.
// This type prevents referenced assets from being traversed from dependencies
// along the untyped edge, and enables traversal to referenced bundles that are
// not directly connected to bundle group nodes.
references: 4,
// Signals that the dependency is internally resolvable via the bundle's ancestry,
// and that the bundle connected to the dependency is not necessary for the source bundle.
internal_async: 5
};
function makeReadOnlySet(set) {
return new Proxy(set, {
get(target, property) {
if (property === 'delete' || property === 'add' || property === 'clear') {
return undefined;
} else {
// $FlowFixMe[incompatible-type]
let value = target[property];
return typeof value === 'function' ? value.bind(target) : value;
}
}
});
}
/**
* Stores assets, dependencies, bundle groups, bundles, and the relationships between them.
* The BundleGraph is passed to the bundler plugin wrapped in a MutableBundleGraph,
* and is passed to packagers and optimizers wrapped in the public BundleGraph object, both
* of which implement public api for this structure. This is the internal structure.
*/
class BundleGraph {
/** A set of all existing concise asset ids present in the BundleGraph */
/** Maps full asset ids (currently 32-character strings) to concise ids (minimum of 5 character strings) */
/**
* A cache of bundle hashes by bundle id.
*
* 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.
* It needs to be exposed in BundlerRunner for now based on how applying runtimes works and the
* BundlerRunner takes care of invalidating hashes when runtimes are applied, but this is not ideal.
*/
_targetEntryRoots = new Map();
/** The internal core Graph structure */
_bundlePublicIds /*: Set<string> */ = new Set();
constructor({
graph,
publicIdByAssetId,
assetPublicIds,
bundleContentHashes
}) {
this._graph = graph;
this._assetPublicIds = assetPublicIds;
this._publicIdByAssetId = publicIdByAssetId;
this._bundleContentHashes = bundleContentHashes;
}
/**
* Produce a BundleGraph from an AssetGraph by removing asset groups and retargeting dependencies
* based on the symbol data (resolving side-effect free reexports).
*/
static fromAssetGraph(assetGraph, isProduction, publicIdByAssetId = new Map(), assetPublicIds = new Set()) {
let graph = new (_graph().ContentGraph)();
let assetGroupIds = new Map();
let dependencies = new Map();
let assetGraphNodeIdToBundleGraphNodeId = new Map();
let assetGraphRootNode = assetGraph.rootNodeId != null ? assetGraph.getNode(assetGraph.rootNodeId) : null;
(0, _assert().default)(assetGraphRootNode != null && assetGraphRootNode.type === 'root');
assetGraph.dfsFast(nodeId => {
let node = assetGraph.getNode(nodeId);
if (node != null && 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 = (0, _utils2.getPublicId)(assetId, existing => assetPublicIds.has(existing));
publicIdByAssetId.set(assetId, publicId);
assetPublicIds.add(publicId);
}
} else if (node != null && 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 = (0, _nullthrows().default)(assetGraph.getNode(nodeId));
if (node.type === 'dependency' && node.value.symbols != null &&
// Disable in dev mode because this feature is at odds with safeToIncrementallyBundle
isProduction) {
let nodeValueSymbols = node.value.symbols;
// asset -> symbols that should be imported directly from that asset
let targets = new (_utils().DefaultMap)(() => 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 retargeting 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 retargeting if the dependency only points to a single asset (e.g. CSS modules)
!hasAmbiguousSymbols &&
// It doesn't make sense to retarget dependencies where `*` is used, because the
// retargeting won't enable any benefits in that case (apart from potentially even more
// code being generated).
!node.usedSymbolsUp.has('*') &&
// 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 === _types.Priority.sync &&
// For every asset, no symbol is imported multiple times (with a different local name).
// Don't retarget because this cannot be resolved without also changing the asset symbols
// (and the asset content itself).
[...targets].every(([, t]) => new Set([...t.values()]).size === t.size)) {
var _nodeValueSymbols$get;
let isReexportAll = ((_nodeValueSymbols$get = nodeValueSymbols.get('*')) === null || _nodeValueSymbols$get === void 0 ? void 0 : _nodeValueSymbols$get.local) === '*';
let reexportAllLoc = isReexportAll ? (0, _nullthrows().default)(nodeValueSymbols.get('*')).loc : undefined;
// TODO adjust sourceAssetIdNode.value.dependencies ?
let deps = [
// Keep the original dependency
{
asset: null,
dep: graph.addNodeByContentKey(node.id, {
...node,
value: {
...node.value,
symbols: new Map([...nodeValueSymbols].filter(([k]) => externalSymbols.has(k)))
},
usedSymbolsUp: new Map([...node.usedSymbolsUp].filter(([k]) => externalSymbols.has(k))),
usedSymbolsDown: new Set(),
excluded: externalSymbols.size === 0
})
}, ...[...targets].map(([asset, target]) => {
let newNodeId = (0, _rust().hashString)(node.id + [...target.keys()].join(','));
let symbols = new Map();
for (let [as, from] of target) {
let existing = nodeValueSymbols.get(as);
if (existing) {
symbols.set(from, {
...existing,
meta: {
rewritten: as
}
});
} else {
(0, _assert().default)(isReexportAll);
if (as === from) {
// Keep the export-all for non-renamed reexports, this still correctly models
// ambiguous resolution with multiple export-alls.
symbols.set('*', {
isWeak: true,
local: '*',
loc: reexportAllLoc
});
} else {
let local = `${node.value.id}$rewrite$${asset}$${from}`;
symbols.set(from, {
isWeak: true,
local,
loc: reexportAllLoc
});
if (node.value.sourceAssetId != null) {
let sourceAssetId = (0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(assetGraph.getNodeIdByContentKey(node.value.sourceAssetId)));
let sourceAsset = (0, _nullthrows().default)(graph.getNode(sourceAssetId));
(0, _assert().default)(sourceAsset.type === 'asset');
let sourceAssetSymbols = sourceAsset.value.symbols;
if (sourceAssetSymbols) {
// The `as == from` case above should handle multiple export-alls causing
// ambiguous resolution. So the current symbol is unambiguous and shouldn't
// already exist on the importer.
(0, _assert().default)(!sourceAssetSymbols.has(as));
sourceAssetSymbols.set(as, {
loc: reexportAllLoc,
local: local
});
}
}
}
}
}
let usedSymbolsUp = new Map([...node.usedSymbolsUp].filter(([k]) => target.has(k) || k === '*').map(([k, v]) => [target.get(k) ?? k, v]));
return {
asset,
dep: graph.addNodeByContentKey(newNodeId, {
...node,
id: newNodeId,
value: {
...node.value,
id: newNodeId,
symbols
},
usedSymbolsUp,
// This is only a temporary helper needed during symbol propagation and is never
// read afterwards (and also not exposed through the public API).
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.
else if (node.type !== 'asset_group') {
let nodeToAdd = node.type === 'asset' ? {
...node,
value: {
...node.value,
symbols: new Map(node.value.symbols)
}
} : node;
let bundleGraphNodeId = graph.addNodeByContentKey(node.id, nodeToAdd);
if (node.id === (assetGraphRootNode === null || assetGraphRootNode === void 0 ? void 0 : assetGraphRootNode.id)) {
graph.setRootNodeId(bundleGraphNodeId);
}
assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId);
}
for (let id of assetGraph.getNodeIdsConnectedFrom(nodeId)) {
walk(id);
}
}
walk((0, _nullthrows().default)(assetGraph.rootNodeId));
for (let edge of assetGraph.getAllEdges()) {
var _dependencies$get, _assetGroupIds$get;
if (assetGroupIds.has(edge.from)) {
continue;
}
if (dependencies.has(edge.from)) {
// Discard previous edge, insert outgoing edges for all split dependencies
for (let {
asset,
dep
} of (0, _nullthrows().default)(dependencies.get(edge.from))) {
if (asset != null) {
graph.addEdge(dep, (0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(assetGraph.getNodeIdByContentKey(asset))));
}
}
continue;
}
if (!assetGraphNodeIdToBundleGraphNodeId.has(edge.from)) {
continue;
}
let to = ((_dependencies$get = dependencies.get(edge.to)) === null || _dependencies$get === void 0 ? void 0 : _dependencies$get.map(v => v.dep)) ?? ((_assetGroupIds$get = assetGroupIds.get(edge.to)) === null || _assetGroupIds$get === void 0 ? void 0 : _assetGroupIds$get.map(id => (0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(id)))) ?? [(0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(edge.to))];
for (let t of to) {
graph.addEdge((0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(edge.from)), t);
}
}
return new BundleGraph({
graph,
assetPublicIds,
bundleContentHashes: new Map(),
publicIdByAssetId
});
}
serialize() {
return {
$$raw: true,
graph: this._graph.serialize(),
assetPublicIds: this._assetPublicIds,
bundleContentHashes: this._bundleContentHashes,
publicIdByAssetId: this._publicIdByAssetId
};
}
static deserialize(serialized) {
return new BundleGraph({
graph: _graph().ContentGraph.deserialize(serialized.graph),
assetPublicIds: serialized.assetPublicIds,
bundleContentHashes: serialized.bundleContentHashes,
publicIdByAssetId: serialized.publicIdByAssetId
});
}
addAssetToBundle(asset, bundle) {
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
this._graph.addEdge(bundleNodeId, this._graph.getNodeIdByContentKey(asset.id), bundleGraphEdgeTypes.contains);
this._graph.addEdge(bundleNodeId, this._graph.getNodeIdByContentKey(asset.id));
let dependencies = this.getDependencies(asset);
for (let dependency of dependencies) {
let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id);
this._graph.addEdge(bundleNodeId, dependencyNodeId, bundleGraphEdgeTypes.contains);
for (let [bundleGroupNodeId, bundleGroupNode] of this._graph.getNodeIdsConnectedFrom(dependencyNodeId).map(id => [id, (0, _nullthrows().default)(this._graph.getNode(id))]).filter(([, node]) => node.type === 'bundle_group')) {
(0, _assert().default)(bundleGroupNode.type === 'bundle_group');
this._graph.addEdge(bundleNodeId, bundleGroupNodeId, bundleGraphEdgeTypes.bundle);
}
// If the dependency references a target bundle, add a reference edge from
// the source bundle to the dependency for easy traversal.
// TODO: Consider bundle being created from dependency
if (this._graph.getNodeIdsConnectedFrom(dependencyNodeId, bundleGraphEdgeTypes.references).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).some(node => node.type === 'bundle')) {
this._graph.addEdge(bundleNodeId, dependencyNodeId, bundleGraphEdgeTypes.references);
}
}
}
addAssetGraphToBundle(asset, bundle, shouldSkipDependency = d => this.isDependencySkipped(d)) {
let assetNodeId = this._graph.getNodeIdByContentKey(asset.id);
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
// The root asset should be reached directly from the bundle in traversal.
// Its children will be traversed from there.
this._graph.addEdge(bundleNodeId, assetNodeId);
this._graph.traverse((nodeId, _, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'bundle_group') {
actions.skipChildren();
return;
}
if (node.type === 'dependency' && shouldSkipDependency(node.value)) {
actions.skipChildren();
return;
}
if (node.type === 'asset' || node.type === 'dependency') {
this._graph.addEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.contains);
}
if (node.type === 'dependency') {
for (let [bundleGroupNodeId, bundleGroupNode] of this._graph.getNodeIdsConnectedFrom(nodeId).map(id => [id, (0, _nullthrows().default)(this._graph.getNode(id))]).filter(([, node]) => node.type === 'bundle_group')) {
(0, _assert().default)(bundleGroupNode.type === 'bundle_group');
this._graph.addEdge(bundleNodeId, bundleGroupNodeId, bundleGraphEdgeTypes.bundle);
}
// If the dependency references a target bundle, add a reference edge from
// the source bundle to the dependency for easy traversal.
if (this._graph.getNodeIdsConnectedFrom(nodeId, bundleGraphEdgeTypes.references).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).some(node => node.type === 'bundle')) {
this._graph.addEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.references);
this.markDependencyReferenceable(node.value);
//all bundles that have this dependency need to have an edge from bundle to that dependency
}
}
}, assetNodeId);
this._bundleContentHashes.delete(bundle.id);
}
markDependencyReferenceable(dependency) {
for (let bundle of this.getBundlesWithDependency(dependency)) {
this._graph.addEdge(this._graph.getNodeIdByContentKey(bundle.id), this._graph.getNodeIdByContentKey(dependency.id), bundleGraphEdgeTypes.references);
}
}
addEntryToBundle(asset, bundle, shouldSkipDependency) {
this.addAssetGraphToBundle(asset, bundle, shouldSkipDependency);
if (!bundle.entryAssetIds.includes(asset.id)) {
bundle.entryAssetIds.push(asset.id);
}
}
internalizeAsyncDependency(bundle, dependency) {
if (dependency.priority === _types.Priority.sync) {
throw new Error('Expected an async dependency');
}
// It's possible for internalized async dependencies to not have
// reference edges and still have untyped edges.
// TODO: Maybe don't use internalized async edges at all?
let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id);
let resolved = this.getResolvedAsset(dependency);
if (resolved) {
let resolvedNodeId = this._graph.getNodeIdByContentKey(resolved.id);
if (!this._graph.hasEdge(dependencyNodeId, resolvedNodeId, bundleGraphEdgeTypes.references)) {
this._graph.addEdge(dependencyNodeId, resolvedNodeId, bundleGraphEdgeTypes.references);
this._graph.removeEdge(dependencyNodeId, resolvedNodeId);
}
}
this._graph.addEdge(this._graph.getNodeIdByContentKey(bundle.id), this._graph.getNodeIdByContentKey(dependency.id), bundleGraphEdgeTypes.internal_async);
this._removeExternalDependency(bundle, dependency);
}
isDependencySkipped(dependency) {
let node = this._graph.getNodeByContentKey(dependency.id);
(0, _assert().default)(node && node.type === 'dependency');
return !!node.hasDeferred || node.excluded;
}
getParentBundlesOfBundleGroup(bundleGroup) {
return this._graph.getNodeIdsConnectedTo(this._graph.getNodeIdByContentKey((0, _utils2.getBundleGroupId)(bundleGroup)), bundleGraphEdgeTypes.bundle).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle').map(node => {
(0, _assert().default)(node.type === 'bundle');
return node.value;
});
}
resolveAsyncDependency(dependency, bundle) {
let depNodeId = this._graph.getNodeIdByContentKey(dependency.id);
let bundleNodeId = bundle != null ? this._graph.getNodeIdByContentKey(bundle.id) : null;
if (bundleNodeId != null && this._graph.hasEdge(bundleNodeId, depNodeId, bundleGraphEdgeTypes.internal_async)) {
let referencedAssetNodeIds = this._graph.getNodeIdsConnectedFrom(depNodeId, bundleGraphEdgeTypes.references);
let resolved;
if (referencedAssetNodeIds.length === 0) {
resolved = this.getResolvedAsset(dependency, bundle);
} else if (referencedAssetNodeIds.length === 1) {
let referencedAssetNode = this._graph.getNode(referencedAssetNodeIds[0]);
// If a referenced asset already exists, resolve this dependency to it.
(0, _assert().default)((referencedAssetNode === null || referencedAssetNode === void 0 ? void 0 : referencedAssetNode.type) === 'asset');
resolved = referencedAssetNode.value;
} else {
throw new Error('Dependencies can only reference one asset');
}
if (resolved == null) {
return;
} else {
return {
type: 'asset',
value: resolved
};
}
}
let node = this._graph.getNodeIdsConnectedFrom(this._graph.getNodeIdByContentKey(dependency.id)).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).find(node => node.type === 'bundle_group');
if (node == null) {
return;
}
(0, _assert().default)(node.type === 'bundle_group');
return {
type: 'bundle_group',
value: node.value
};
}
// eslint-disable-next-line no-unused-vars
getReferencedBundle(dependency, fromBundle) {
let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id);
// Find an attached bundle via a reference edge (e.g. from createAssetReference).
let bundleNodes = this._graph.getNodeIdsConnectedFrom(dependencyNodeId, bundleGraphEdgeTypes.references).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle');
if (bundleNodes.length) {
let bundleNode = bundleNodes.find(b => b.type === 'bundle' && b.value.type === fromBundle.type) || bundleNodes[0];
(0, _assert().default)(bundleNode.type === 'bundle');
return bundleNode.value;
}
// If this dependency is async, there will be a bundle group attached to it.
let node = this._graph.getNodeIdsConnectedFrom(dependencyNodeId).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).find(node => node.type === 'bundle_group');
if (node != null) {
(0, _assert().default)(node.type === 'bundle_group');
return this.getBundlesInBundleGroup(node.value, {
includeInline: true
}).find(b => {
let mainEntryId = b.entryAssetIds[b.entryAssetIds.length - 1];
return mainEntryId != null && node.value.entryAssetId === mainEntryId;
});
}
}
removeAssetGraphFromBundle(asset, bundle) {
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
let assetNodeId = this._graph.getNodeIdByContentKey(asset.id);
// Remove all contains edges from the bundle to the nodes in the asset's
// subgraph.
this._graph.traverse((nodeId, context, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'bundle_group') {
actions.skipChildren();
return;
}
if (node.type !== 'dependency' && node.type !== 'asset') {
return;
}
if (this._graph.hasEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.contains)) {
this._graph.removeEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.contains,
// Removing this contains edge should not orphan the connected node. This
// is disabled for performance reasons as these edges are removed as part
// of a traversal, and checking for orphans becomes quite expensive in
// aggregate.
false /* removeOrphans */);
} else {
actions.skipChildren();
}
if (node.type === 'asset' && this._graph.hasEdge(bundleNodeId, nodeId)) {
// Remove the untyped edge from the bundle to the node (it's an entry)
this._graph.removeEdge(bundleNodeId, nodeId);
let entryIndex = bundle.entryAssetIds.indexOf(node.value.id);
if (entryIndex >= 0) {
// Shared bundles have untyped edges to their asset graphs but don't
// have entry assets. For those that have entry asset ids, remove them.
bundle.entryAssetIds.splice(entryIndex, 1);
}
}
if (node.type === 'dependency') {
this._removeExternalDependency(bundle, node.value);
if (this._graph.hasEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.references)) {
this._graph.addEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.references);
this.markDependencyReferenceable(node.value);
}
if (this._graph.hasEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.internal_async)) {
this._graph.removeEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.internal_async);
}
}
}, assetNodeId);
// Remove bundle node if it no longer has any entry assets
if (this._graph.getNodeIdsConnectedFrom(bundleNodeId).length === 0) {
this.removeBundle(bundle);
}
this._bundleContentHashes.delete(bundle.id);
}
/**
* Remove a bundle from the bundle graph. Remove its bundle group if it is
* the only bundle in the group.
*/
removeBundle(bundle) {
// Remove bundle node if it no longer has any entry assets
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
let bundleGroupNodeIds = this._graph.getNodeIdsConnectedTo(bundleNodeId, bundleGraphEdgeTypes.bundle);
this._graph.removeNode(bundleNodeId);
let removedBundleGroups = new Set();
// Remove bundle group node if it no longer has any bundles
for (let bundleGroupNodeId of bundleGroupNodeIds) {
let bundleGroupNode = (0, _nullthrows().default)(this._graph.getNode(bundleGroupNodeId));
(0, _assert().default)(bundleGroupNode.type === 'bundle_group');
let bundleGroup = bundleGroupNode.value;
if (
// If the bundle group's entry asset belongs to this bundle, the group
// was created because of this bundle. Remove the group.
bundle.entryAssetIds.includes(bundleGroup.entryAssetId) ||
// If the bundle group is now empty, remove it.
this.getBundlesInBundleGroup(bundleGroup, {
includeInline: true
}).length === 0) {
removedBundleGroups.add(bundleGroup);
this.removeBundleGroup(bundleGroup);
}
}
this._bundleContentHashes.delete(bundle.id);
return removedBundleGroups;
}
removeBundleGroup(bundleGroup) {
let bundleGroupNode = (0, _nullthrows().default)(this._graph.getNodeByContentKey((0, _utils2.getBundleGroupId)(bundleGroup)));
(0, _assert().default)(bundleGroupNode.type === 'bundle_group');
let bundlesInGroup = this.getBundlesInBundleGroup(bundleGroupNode.value, {
includeInline: true
});
for (let bundle of bundlesInGroup) {
if (this.getBundleGroupsContainingBundle(bundle).length === 1) {
let removedBundleGroups = this.removeBundle(bundle);
if (removedBundleGroups.has(bundleGroup)) {
// This function can be reentered through removeBundle above. In the case this
// bundle group has already been removed, stop.
return;
}
}
}
// This function can be reentered through removeBundle above. In this case,
// the node may already been removed.
if (this._graph.hasContentKey(bundleGroupNode.id)) {
this._graph.removeNode(this._graph.getNodeIdByContentKey(bundleGroupNode.id));
}
(0, _assert().default)(bundlesInGroup.every(bundle => this.getBundleGroupsContainingBundle(bundle).length > 0));
}
_removeExternalDependency(bundle, dependency) {
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
for (let bundleGroupNode of this._graph.getNodeIdsConnectedFrom(this._graph.getNodeIdByContentKey(dependency.id)).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle_group')) {
let bundleGroupNodeId = this._graph.getNodeIdByContentKey(bundleGroupNode.id);
if (!this._graph.hasEdge(bundleNodeId, bundleGroupNodeId, bundleGraphEdgeTypes.bundle)) {
continue;
}
let inboundDependencies = this._graph.getNodeIdsConnectedTo(bundleGroupNodeId).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'dependency').map(node => {
(0, _assert().default)(node.type === 'dependency');
return node.value;
});
// If every inbound dependency to this bundle group does not belong to this bundle,
// or the dependency is internal to the bundle, then the connection between
// this bundle and the group is safe to remove.
if (inboundDependencies.every(dependency => dependency.specifierType !== _types.SpecifierType.url && (!this.bundleHasDependency(bundle, dependency) || this._graph.hasEdge(bundleNodeId, this._graph.getNodeIdByContentKey(dependency.id), bundleGraphEdgeTypes.internal_async)))) {
this._graph.removeEdge(bundleNodeId, bundleGroupNodeId, bundleGraphEdgeTypes.bundle);
}
}
}
createAssetReference(dependency, asset, bundle) {
let dependencyId = this._graph.getNodeIdByContentKey(dependency.id);
let assetId = this._graph.getNodeIdByContentKey(asset.id);
let bundleId = this._graph.getNodeIdByContentKey(bundle.id);
this._graph.addEdge(dependencyId, assetId, bundleGraphEdgeTypes.references);
this._graph.addEdge(dependencyId, bundleId, bundleGraphEdgeTypes.references);
this.markDependencyReferenceable(dependency);
if (this._graph.hasEdge(dependencyId, assetId)) {
this._graph.removeEdge(dependencyId, assetId);
}
}
createBundleReference(from, to) {
this._graph.addEdge(this._graph.getNodeIdByContentKey(from.id), this._graph.getNodeIdByContentKey(to.id), bundleGraphEdgeTypes.references);
}
getBundlesWithAsset(asset) {
return this._graph.getNodeIdsConnectedTo(this._graph.getNodeIdByContentKey(asset.id), bundleGraphEdgeTypes.contains).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle').map(node => {
(0, _assert().default)(node.type === 'bundle');
return node.value;
});
}
getBundlesWithDependency(dependency) {
return this._graph.getNodeIdsConnectedTo((0, _nullthrows().default)(this._graph.getNodeIdByContentKey(dependency.id)), bundleGraphEdgeTypes.contains).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle').map(node => {
(0, _assert().default)(node.type === 'bundle');
return node.value;
});
}
getDependencyAssets(dependency) {
return this._graph.getNodeIdsConnectedFrom(this._graph.getNodeIdByContentKey(dependency.id)).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'asset').map(node => {
(0, _assert().default)(node.type === 'asset');
return node.value;
});
}
getResolvedAsset(dep, bundle) {
let assets = this.getDependencyAssets(dep);
let firstAsset = assets[0];
let resolved =
// If no bundle is specified, use the first concrete asset.
bundle == null ? firstAsset :
// Otherwise, find the first asset that belongs to this bundle.
assets.find(asset => this.bundleHasAsset(bundle, asset)) || assets.find(a => a.type === bundle.type) || firstAsset;
// If a resolution still hasn't been found, return the first referenced asset.
if (resolved == null) {
let potential = [];
this._graph.traverse((nodeId, _, traversal) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'asset') {
potential.push(node.value);
} else if (node.id !== dep.id) {
traversal.skipChildren();
}
}, this._graph.getNodeIdByContentKey(dep.id), bundleGraphEdgeTypes.references);
if (bundle) {
resolved = potential.find(a => a.type === bundle.type);
}
resolved ||= potential[0];
}
return resolved;
}
getDependencies(asset) {
let nodeId = this._graph.getNodeIdByContentKey(asset.id);
return this._graph.getNodeIdsConnectedFrom(nodeId).map(id => {
let node = (0, _nullthrows().default)(this._graph.getNode(id));
(0, _assert().default)(node.type === 'dependency');
return node.value;
});
}
traverseAssets(bundle, visit, startAsset) {
return this.traverseBundle(bundle, (0, _graph().mapVisitor)(node => node.type === 'asset' ? node.value : null, visit), startAsset);
}
isAssetReferenced(bundle, asset) {
// If the asset is available in multiple bundles in the same target, it's referenced.
if (this.getBundlesWithAsset(asset).filter(b => b.target.name === bundle.target.name && b.target.distDir === bundle.target.distDir).length > 1) {
return true;
}
let assetNodeId = (0, _nullthrows().default)(this._graph.getNodeIdByContentKey(asset.id));
if (this._graph.getNodeIdsConnectedTo(assetNodeId, bundleGraphEdgeTypes.references).some(id => {
let node = this._graph.getNode(id);
return (node === null || node === void 0 ? void 0 : node.type) === 'dependency' && !node.value.isEntry && (this._graph.getNodeIdsConnectedFrom(id).some(id => {
var _this$_graph$getNode;
return ((_this$_graph$getNode = this._graph.getNode(id)) === null || _this$_graph$getNode === void 0 ? void 0 : _this$_graph$getNode.type) === 'bundle_group';
}) || this._graph.getNodeIdsConnectedTo(id, bundleGraphEdgeTypes.internal_async).length > 0);
})) {
// If this asset is referenced by any async dependency, it's referenced.
return true;
}
let dependencies = this._graph.getNodeIdsConnectedTo(assetNodeId).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'dependency').map(node => {
(0, _assert().default)(node.type === 'dependency');
return node.value;
});
const bundleHasReference = bundle => {
return !this.bundleHasAsset(bundle, asset) && dependencies.some(dependency => this.bundleHasDependency(bundle, dependency));
};
let visitedBundles = new Set();
let siblingBundles = new Set(this.getBundleGroupsContainingBundle(bundle).flatMap(bundleGroup => this.getBundlesInBundleGroup(bundleGroup, {
includeInline: true
})));
// Check if any of this bundle's descendants, referencers, bundles referenced
// by referencers, or descendants of its referencers use the asset without
// an explicit reference edge. This can happen if e.g. the asset has been
// deduplicated.
return [...siblingBundles].some(referencer => {
let isReferenced = false;
this.traverseBundles((descendant, _, actions) => {
if (descendant.id === bundle.id) {
return;
}
if (visitedBundles.has(descendant)) {
actions.skipChildren();
return;
}
visitedBundles.add(descendant);
if (descendant.type !== bundle.type || _Environment.ISOLATED_ENVS.has(descendant.env.context)) {
actions.skipChildren();
return;
}
if (bundleHasReference(descendant)) {
isReferenced = true;
actions.stop();
}
}, referencer);
return isReferenced;
});
}
hasParentBundleOfType(bundle, type) {
let parents = this.getParentBundles(bundle);
return parents.length > 0 && parents.every(parent => parent.type === type && parent.env.context === bundle.env.context);
}
getParentBundles(bundle) {
let parentBundles = new Set();
for (let bundleGroup of this.getBundleGroupsContainingBundle(bundle)) {
for (let parentBundle of this.getParentBundlesOfBundleGroup(bundleGroup)) {
parentBundles.add(parentBundle);
}
}
return [...parentBundles];
}
isAssetReachableFromBundle(asset, bundle) {
// If a bundle's environment is isolated, it can't access assets present
// in any ancestor bundles. Don't consider any assets reachable.
if (_Environment.ISOLATED_ENVS.has(bundle.env.context) || !bundle.isSplittable || bundle.bundleBehavior === _types.BundleBehavior.isolated || bundle.bundleBehavior === _types.BundleBehavior.inline) {
return false;
}
// For an asset to be reachable from a bundle, it must either exist in a sibling bundle,
// or in an ancestor bundle group reachable from all parent bundles.
let bundleGroups = this.getBundleGroupsContainingBundle(bundle);
return bundleGroups.every(bundleGroup => {
// If the asset is in any sibling bundles of the original bundle, it is reachable.
let bundles = this.getBundlesInBundleGroup(bundleGroup);
if (bundles.some(b => b.id !== bundle.id && b.bundleBehavior !== _types.BundleBehavior.isolated && b.bundleBehavior !== _types.BundleBehavior.inline && this.bundleHasAsset(b, asset))) {
return true;
}
// Get a list of parent bundle nodes pointing to the bundle group
let parentBundleNodes = this._graph.getNodeIdsConnectedTo(this._graph.getNodeIdByContentKey((0, _utils2.getBundleGroupId)(bundleGroup)), bundleGraphEdgeTypes.bundle);
// Check that every parent bundle has a bundle group in its ancestry that contains the asset.
return parentBundleNodes.every(bundleNodeId => {
let bundleNode = (0, _nullthrows().default)(this._graph.getNode(bundleNodeId));
if (bundleNode.type !== 'bundle' || bundleNode.value.bundleBehavior === _types.BundleBehavior.isolated || bundleNode.value.bundleBehavior === _types.BundleBehavior.inline) {
return false;
}
let isReachable = true;
this._graph.traverseAncestors(bundleNodeId, (nodeId, ctx, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
// If we've reached the root or a context change without
// finding this asset in the ancestry, it is not reachable.
if (node.type === 'root' || node.type === 'bundle' && (node.value.id === bundle.id || _Environment.ISOLATED_ENVS.has(node.value.env.context))) {
isReachable = false;
actions.stop();
return;
}
if (node.type === 'bundle_group') {
let childBundles = this.getBundlesInBundleGroup(node.value);
if (childBundles.some(b => b.id !== bundle.id && b.bundleBehavior !== _types.BundleBehavior.isolated && b.bundleBehavior !== _types.BundleBehavior.inline && this.bundleHasAsset(b, asset))) {
actions.skipChildren();
}
}
}, [bundleGraphEdgeTypes.references, bundleGraphEdgeTypes.bundle]);
return isReachable;
});
});
}
/**
* TODO: Document why this works like this & why visitor order matters
* on these use-cases.
*/
traverseBundle(bundle, visit, startAsset) {
let entries = !startAsset;
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
// A modified DFS traversal which traverses entry assets in the same order
// as their ids appear in `bundle.entryAssetIds`.
return this._graph.dfs({
visit: (0, _graph().mapVisitor)((nodeId, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (nodeId === bundleNodeId) {
return;
}
if (node.type === 'dependency' || node.type === 'asset') {
if (this._graph.hasEdge(bundleNodeId, nodeId, bundleGraphEdgeTypes.contains)) {
return node;
}
}
actions.skipChildren();
}, visit),
startNodeId: startAsset ? this._graph.getNodeIdByContentKey(startAsset.id) : bundleNodeId,
getChildren: nodeId => {
let children = this._graph.getNodeIdsConnectedFrom(nodeId).map(id => [id, (0, _nullthrows().default)(this._graph.getNode(id))]);
let sorted = entries && bundle.entryAssetIds.length > 0 ? children.sort(([, a], [, b]) => {
let aIndex = bundle.entryAssetIds.indexOf(a.id);
let bIndex = bundle.entryAssetIds.indexOf(b.id);
if (aIndex === bIndex) {
// If both don't exist in the entry asset list, or
// otherwise have the same index.
return 0;
} else if (aIndex === -1) {
return 1;
} else if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
}) : children;
entries = false;
return sorted.map(([id]) => id);
}
});
}
traverse(visit, start) {
return this._graph.filteredTraverse(nodeId => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'asset' || node.type === 'dependency') {
return node;
}
}, visit, start ? this._graph.getNodeIdByContentKey(start.id) : undefined,
// start with root
_graph().ALL_EDGE_TYPES);
}
getChildBundles(bundle) {
let siblings = new Set(this.getReferencedBundles(bundle));
let bundles = [];
this.traverseBundles((b, _, actions) => {
if (bundle.id === b.id) {
return;
}
if (!siblings.has(b)) {
bundles.push(b);
}
actions.skipChildren();
}, bundle);
return bundles;
}
traverseBundles(visit, startBundle) {
return this._graph.filteredTraverse(nodeId => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
return node.type === 'bundle' ? node.value : null;
}, visit, startBundle ? this._graph.getNodeIdByContentKey(startBundle.id) : null, [bundleGraphEdgeTypes.bundle, bundleGraphEdgeTypes.references]);
}
getBundles(opts) {
let bundles = [];
this.traverseBundles(bundle => {
if (opts !== null && opts !== void 0 && opts.includeInline || bundle.bundleBehavior !== _types.BundleBehavior.inline) {
bundles.push(bundle);
}
});
return bundles;
}
getTotalSize(asset) {
let size = 0;
this._graph.traverse((nodeId, _, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'bundle_group') {
actions.skipChildren();
return;
}
if (node.type === 'asset') {
size += node.value.stats.size;
}
}, this._graph.getNodeIdByContentKey(asset.id));
return size;
}
getReferencingBundles(bundle) {
let referencingBundles = new Set();
this._graph.traverseAncestors(this._graph.getNodeIdByContentKey(bundle.id), nodeId => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type === 'bundle' && node.value.id !== bundle.id) {
referencingBundles.add(node.value);
}
}, bundleGraphEdgeTypes.references);
return [...referencingBundles];
}
getBundleGroupsContainingBundle(bundle) {
let bundleGroups = new Set();
for (let currentBundle of [bundle, ...this.getReferencingBundles(bundle)]) {
for (let bundleGroup of this.getDirectParentBundleGroups(currentBundle)) {
bundleGroups.add(bundleGroup);
}
}
return [...bundleGroups];
}
getDirectParentBundleGroups(bundle) {
return this._graph.getNodeIdsConnectedTo((0, _nullthrows().default)(this._graph.getNodeIdByContentKey(bundle.id)), bundleGraphEdgeTypes.bundle).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(node => node.type === 'bundle_group').map(node => {
(0, _assert().default)(node.type === 'bundle_group');
return node.value;
});
}
getBundlesInBundleGroup(bundleGroup, opts) {
let recursive = (opts === null || opts === void 0 ? void 0 : opts.recursive) ?? true;
let includeInline = (opts === null || opts === void 0 ? void 0 : opts.includeInline) ?? false;
let includeIsolated = (opts === null || opts === void 0 ? void 0 : opts.includeIsolated) ?? true;
let bundles = new Set();
for (let bundleNodeId of this._graph.getNodeIdsConnectedFrom(this._graph.getNodeIdByContentKey((0, _utils2.getBundleGroupId)(bundleGroup)), bundleGraphEdgeTypes.bundle)) {
let bundleNode = (0, _nullthrows().default)(this._graph.getNode(bundleNodeId));
(0, _assert().default)(bundleNode.type === 'bundle');
let bundle = bundleNode.value;
if (bundle.bundleBehavior == null || includeInline && bundle.bundleBehavior === _types.BundleBehavior.inline || includeIsolated && bundle.bundleBehavior === _types.BundleBehavior.isolated) {
bundles.add(bundle);
}
if (recursive) {
for (let referencedBundle of this.getReferencedBundles(bundle, opts)) {
bundles.add(referencedBundle);
}
}
}
return [...bundles];
}
getReferencedBundles(bundle, opts) {
let recursive = (opts === null || opts === void 0 ? void 0 : opts.recursive) ?? true;
let includeInline = (opts === null || opts === void 0 ? void 0 : opts.includeInline) ?? false;
let includeIsolated = (opts === null || opts === void 0 ? void 0 : opts.includeIsolated) ?? true;
let referencedBundles = new Set();
this._graph.dfs({
visit: (nodeId, _, actions) => {
let node = (0, _nullthrows().default)(this._graph.getNode(nodeId));
if (node.type !== 'bundle') {
return;
}
if (node.value.id === bundle.id) {
return;
}
if (node.value.bundleBehavior == null || includeInline && node.value.bundleBehavior === _types.BundleBehavior.inline || includeIsolated && node.value.bundleBehavior === _types.BundleBehavior.isolated) {
referencedBundles.add(node.value);
} else if (node.value.bundleBehavior === _types.BundleBehavior.isolated) {
actions.skipChildren();
}
if (!recursive) {
actions.skipChildren();
}
},
startNodeId: this._graph.getNodeIdByContentKey(bundle.id),
getChildren: nodeId =>
// Shared bundles seem to depend on being used in the opposite order
// they were added.
// TODO: Should this be the case?
this._graph.getNodeIdsConnectedFrom(nodeId, bundleGraphEdgeTypes.references)
});
return [...referencedBundles];
}
getIncomingDependencies(asset) {
if (!this._graph.hasContentKey(asset.id)) {
return [];
}
// Dependencies can be a a parent node via an untyped edge (like in the AssetGraph but without AssetGroups)
// or they can be parent nodes via a 'references' edge
return this._graph.getNodeIdsConnectedTo(this._graph.getNodeIdByContentKey(asset.id), _graph().ALL_EDGE_TYPES).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).filter(n => n.type === 'dependency').map(n => {
(0, _assert().default)(n.type === 'dependency');
return n.value;
});
}
getAssetWithDependency(dep) {
if (!this._graph.hasContentKey(dep.id)) {
return null;
}
let res = this._graph.getNodeIdsConnectedTo(this._graph.getNodeIdByContentKey(dep.id));
(0, _assert().default)(res.length <= 1, 'Expected a single asset to be connected to a dependency');
let resNode = this._graph.getNode(res[0]);
if ((resNode === null || resNode === void 0 ? void 0 : resNode.type) === 'asset') {
return resNode.value;
}
}
bundleHasAsset(bundle, asset) {
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
let assetNodeId = this._graph.getNodeIdByContentKey(asset.id);
return this._graph.hasEdge(bundleNodeId, assetNodeId, bundleGraphEdgeTypes.contains);
}
bundleHasDependency(bundle, dependency) {
let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id);
let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id);
return this._graph.hasEdge(bundleNodeId, dependencyNodeId, bundleGraphEdgeTypes.contains);
}
filteredTraverse(bundleNodeId, filter, visit) {
return this._graph.filteredTraverse(filter, visit, bundleNodeId);
}
getSymbolResolution(asset, symbol, boundary) {
var _asset$symbols;
let assetOutside = boundary && !this.bundleHasAsset(boundary, asset);
let identifier = (_asset$symbols = asset.symbols) === null || _asset$symbols === void 0 || (_asset$symbols = _asset$symbols.get(symbol)) === null || _asset$symbols === void 0 ? void 0 : _asset$symbols.local;
if (symbol === '*') {
var _asset$symbols2;
return {
asset,
exportSymbol: '*',
symbol: identifier ?? null,
loc: (_asset$symbols2 = asset.symbols) === null || _asset$symbols2 === void 0 || (_asset$symbols2 =