UNPKG

@parcel/core

Version:
1,058 lines (1,030 loc) • 66.1 kB
"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 =