"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 _hash() { const data = require("@parcel/hash"); _hash = 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(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const 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 }; exports.bundleGraphEdgeTypes = bundleGraphEdgeTypes; 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; } } }); } class BundleGraph { // TODO: These hashes are being invalidated in mutative methods, but this._graph is not a private // property so it is possible to reach in and mutate the graph without invalidating these hashes. // 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(); constructor({ graph, publicIdByAssetId, assetPublicIds, bundleContentHashes, symbolPropagationRan }) { this._graph = graph; this._assetPublicIds = assetPublicIds; this._publicIdByAssetId = publicIdByAssetId; this._bundleContentHashes = bundleContentHashes; this._symbolPropagationRan = symbolPropagationRan; } static fromAssetGraph(assetGraph, publicIdByAssetId = new Map(), assetPublicIds = new Set()) { let graph = new (_graph().ContentGraph)(); let assetGroupIds = new Set(); let assetGraphNodeIdToBundleGraphNodeId = new Map(); let assetGraphRootNode = assetGraph.rootNodeId != null ? assetGraph.getNode(assetGraph.rootNodeId) : null; (0, _assert().default)(assetGraphRootNode != null && assetGraphRootNode.type === 'root'); for (let [nodeId, node] of assetGraph.nodes) { if (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); } } // Don't copy over asset groups into the bundle graph. if (node.type === 'asset_group') { assetGroupIds.add(nodeId); } else { let bundleGraphNodeId = graph.addNodeByContentKey(node.id, node); if (node.id === (assetGraphRootNode === null || assetGraphRootNode === void 0 ? void 0 : assetGraphRootNode.id)) { graph.setRootNodeId(bundleGraphNodeId); } assetGraphNodeIdToBundleGraphNodeId.set(nodeId, bundleGraphNodeId); } } for (let edge of assetGraph.getAllEdges()) { let fromIds; if (assetGroupIds.has(edge.from)) { fromIds = [...assetGraph.inboundEdges.getEdges(edge.from, bundleGraphEdgeTypes.null)]; } else { fromIds = [edge.from]; } for (let from of fromIds) { if (assetGroupIds.has(edge.to)) { for (let to of assetGraph.outboundEdges.getEdges(edge.to, bundleGraphEdgeTypes.null)) { graph.addEdge((0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(from)), (0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(to))); } } else { graph.addEdge((0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(from)), (0, _nullthrows().default)(assetGraphNodeIdToBundleGraphNodeId.get(edge.to))); } } } return new BundleGraph({ graph, assetPublicIds, bundleContentHashes: new Map(), publicIdByAssetId, symbolPropagationRan: assetGraph.symbolPropagationRan }); } serialize() { return { $$raw: true, graph: this._graph.serialize(), assetPublicIds: this._assetPublicIds, bundleContentHashes: this._bundleContentHashes, publicIdByAssetId: this._publicIdByAssetId, symbolPropagationRan: this._symbolPropagationRan }; } static deserialize(serialized) { return new BundleGraph({ graph: _graph().ContentGraph.deserialize(serialized.graph), assetPublicIds: serialized.assetPublicIds, bundleContentHashes: serialized.bundleContentHashes, publicIdByAssetId: serialized.publicIdByAssetId, symbolPropagationRan: serialized.symbolPropagationRan }); } 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); } } }, assetNodeId); this._bundleContentHashes.delete(bundle.id); } 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'); } 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 }; } getReferencedBundle(dependency, fromBundle) { let dependencyNodeId = this._graph.getNodeIdByContentKey(dependency.id); // 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; }); } // Otherwise, it may be a reference to another asset in the same bundle group. // Resolve the dependency to an asset, and look for it in one of the referenced bundles. let referencedBundles = this.getReferencedBundles(fromBundle, { includeInline: true }); let referenced = this._graph.getNodeIdsConnectedFrom(dependencyNodeId, bundleGraphEdgeTypes.references).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).find(node => node.type === 'asset'); if (referenced != null) { (0, _assert().default)(referenced.type === 'asset'); return referencedBundles.find(b => this.bundleHasAsset(b, referenced.value)); } } 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); } 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); } 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 => !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); 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)) || firstAsset; // If a resolution still hasn't been found, return the first referenced asset. if (resolved == null) { this._graph.traverse((nodeId, _, traversal) => { let node = (0, _nullthrows().default)(this._graph.getNode(nodeId)); if (node.type === 'asset') { resolved = node.value; traversal.stop(); } else if (node.id !== dep.id) { traversal.skipChildren(); } }, this._graph.getNodeIdByContentKey(dep.id), bundleGraphEdgeTypes.references); } 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) { return this.traverseBundle(bundle, (0, _graph().mapVisitor)(node => node.type === 'asset' ? node.value : null, visit)); } isAssetReferenced(bundle, asset) { let assetNodeId = (0, _nullthrows().default)(this._graph.getNodeIdByContentKey(asset.id)); if (this._graph.getNodeIdsConnectedTo(assetNodeId, bundleGraphEdgeTypes.references).map(id => this._graph.getNode(id)).some(node => (node === null || node === void 0 ? void 0 : node.type) === 'dependency' && node.value.priority === _types.Priority.lazy && node.value.specifierType !== _types.SpecifierType.url)) { // 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 || descendant.env.context !== bundle.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); } 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 || node.value.env.context !== bundle.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; }); }); } traverseBundle(bundle, visit) { let entries = true; 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: 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) { return this._graph.filteredTraverse(nodeId => { let node = (0, _nullthrows().default)(this._graph.getNode(nodeId)); if (node.type === 'asset' || node.type === 'dependency') { return node; } }, visit, undefined, // start with root // $FlowFixMe _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 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 (opts !== null && opts !== void 0 && opts.includeInline || bundle.bundleBehavior !== _types.BundleBehavior.inline) { bundles.add(bundle); } for (let referencedBundle of this.getReferencedBundles(bundle, { includeInline: true })) { bundles.add(referencedBundle); } } return [...bundles]; } getReferencedBundles(bundle, opts) { var _opts$recursive, _opts$includeInline; let recursive = (_opts$recursive = opts === null || opts === void 0 ? void 0 : opts.recursive) !== null && _opts$recursive !== void 0 ? _opts$recursive : true; let includeInline = (_opts$includeInline = opts === null || opts === void 0 ? void 0 : opts.includeInline) !== null && _opts$includeInline !== void 0 ? _opts$includeInline : false; 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 (includeInline || node.value.bundleBehavior !== _types.BundleBehavior.inline) { referencedBundles.add(node.value); } 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).reverse() }); 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), // $FlowFixMe _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, _asset$symbols$get; let assetOutside = boundary && !this.bundleHasAsset(boundary, asset); let identifier = (_asset$symbols = asset.symbols) === null || _asset$symbols === void 0 ? void 0 : (_asset$symbols$get = _asset$symbols.get(symbol)) === null || _asset$symbols$get === void 0 ? void 0 : _asset$symbols$get.local; if (symbol === '*') { var _asset$symbols2, _asset$symbols2$get; return { asset, exportSymbol: '*', symbol: identifier !== null && identifier !== void 0 ? identifier : null, loc: (_asset$symbols2 = asset.symbols) === null || _asset$symbols2 === void 0 ? void 0 : (_asset$symbols2$get = _asset$symbols2.get(symbol)) === null || _asset$symbols2$get === void 0 ? void 0 : _asset$symbols2$get.loc }; } let found = false; let skipped = false; let deps = this.getDependencies(asset).reverse(); let potentialResults = []; for (let dep of deps) { var _depSymbols$get; let depSymbols = dep.symbols; if (!depSymbols) { found = true; continue; } // If this is a re-export, find the original module. let symbolLookup = new Map([...depSymbols].map(([key, val]) => [val.local, key])); let depSymbol = symbolLookup.get(identifier); if (depSymbol != null) { let resolved = this.getResolvedAsset(dep); if (!resolved || resolved.id === asset.id) { var _asset$symbols3, _asset$symbols3$get; // External module or self-reference return { asset, exportSymbol: symbol, symbol: identifier, loc: (_asset$symbols3 = asset.symbols) === null || _asset$symbols3 === void 0 ? void 0 : (_asset$symbols3$get = _asset$symbols3.get(symbol)) === null || _asset$symbols3$get === void 0 ? void 0 : _asset$symbols3$get.loc }; } if (assetOutside) { // We found the symbol, but `asset` is outside, return `asset` and the original symbol found = true; break; } if (this.isDependencySkipped(dep)) { // We found the symbol and `dep` was skipped skipped = true; break; } let { asset: resolvedAsset, symbol: resolvedSymbol, exportSymbol, loc } = this.getSymbolResolution(resolved, depSymbol, boundary); if (!loc) { var _asset$symbols4, _asset$symbols4$get; // Remember how we got there loc = (_asset$symbols4 = asset.symbols) === null || _asset$symbols4 === void 0 ? void 0 : (_asset$symbols4$get = _asset$symbols4.get(symbol)) === null || _asset$symbols4$get === void 0 ? void 0 : _asset$symbols4$get.loc; } return { asset: resolvedAsset, symbol: resolvedSymbol, exportSymbol, loc }; } // If this module exports wildcards, resolve the original module. // Default exports are excluded from wildcard exports. // Wildcard reexports are never listed in the reexporting asset's symbols. if (identifier == null && ((_depSymbols$get = depSymbols.get('*')) === null || _depSymbols$get === void 0 ? void 0 : _depSymbols$get.local) === '*' && symbol !== 'default') { let resolved = this.getResolvedAsset(dep); if (!resolved) { continue; } let result = this.getSymbolResolution(resolved, symbol, boundary); // We found the symbol if (result.symbol != undefined) { var _resolved$symbols, _resolved$symbols$get; if (assetOutside) { // ..., but `asset` is outside, return `asset` and the original symbol found = true; break; } if (this.isDependencySkipped(dep)) { // We found the symbol and `dep` was skipped skipped = true; break; } return { asset: result.asset, symbol: result.symbol, exportSymbol: result.exportSymbol, loc: (_resolved$symbols = resolved.symbols) === null || _resolved$symbols === void 0 ? void 0 : (_resolved$symbols$get = _resolved$symbols.get(symbol)) === null || _resolved$symbols$get === void 0 ? void 0 : _resolved$symbols$get.loc }; } if (result.symbol === null) { found = true; if (boundary && !this.bundleHasAsset(boundary, result.asset)) { // If the returned asset is outside (and it's the first asset that is outside), return it. if (!assetOutside) { var _resolved$symbols2, _resolved$symbols2$ge; return { asset: result.asset, symbol: result.symbol, exportSymbol: result.exportSymbol, loc: (_resolved$symbols2 = resolved.symbols) === null || _resolved$symbols2 === void 0 ? void 0 : (_resolved$symbols2$ge = _resolved$symbols2.get(symbol)) === null || _resolved$symbols2$ge === void 0 ? void 0 : _resolved$symbols2$ge.loc }; } else { // Otherwise the original asset will be returned at the end. break; } } else { var _resolved$symbols3, _resolved$symbols3$ge; // We didn't find it in this dependency, but it might still be there: bailout. // Continue searching though, with the assumption that there are no conficting reexports // and there might be a another (re)export (where we might statically find the symbol). potentialResults.push({ asset: result.asset, symbol: result.symbol, exportSymbol: result.exportSymbol, loc: (_resolved$symbols3 = resolved.symbols) === null || _resolved$symbols3 === void 0 ? void 0 : (_resolved$symbols3$ge = _resolved$symbols3.get(symbol)) === null || _resolved$symbols3$ge === void 0 ? void 0 : _resolved$symbols3$ge.loc }); } } } } // We didn't find the exact symbol... if (potentialResults.length == 1) { // ..., but if it does exist, it has to be behind this one reexport. return potentialResults[0]; } else { var _asset$symbols5, _asset$symbols6, _asset$symbols6$get; // ... and there is no single reexport, but `bailout` tells us if it might still be exported. return { asset, exportSymbol: symbol, symbol: skipped ? false : found ? null : identifier !== null && identifier !== void 0 ? identifier : (_asset$symbols5 = asset.symbols) !== null && _asset$symbols5 !== void 0 && _asset$symbols5.has('*') ? null : undefined, loc: (_asset$symbols6 = asset.symbols) === null || _asset$symbols6 === void 0 ? void 0 : (_asset$symbols6$get = _asset$symbols6.get(symbol)) === null || _asset$symbols6$get === void 0 ? void 0 : _asset$symbols6$get.loc }; } } getAssetById(contentKey) { let node = this._graph.getNodeByContentKey(contentKey); if (node == null) { throw new Error('Node not found'); } else if (node.type !== 'asset') { throw new Error('Node was not an asset'); } return node.value; } getAssetPublicId(asset) { let publicId = this._publicIdByAssetId.get(asset.id); if (publicId == null) { throw new Error("Asset or it's public id not found"); } return publicId; } getExportedSymbols(asset, boundary) { if (!asset.symbols) { return []; } let symbols = []; for (let symbol of asset.symbols.keys()) { symbols.push({ ...this.getSymbolResolution(asset, symbol, boundary), exportAs: symbol }); } let deps = this.getDependencies(asset); for (let dep of deps) { var _depSymbols$get2; let depSymbols = dep.symbols; if (!depSymbols) continue; if (((_depSymbols$get2 = depSymbols.get('*')) === null || _depSymbols$get2 === void 0 ? void 0 : _depSymbols$get2.local) === '*') { let resolved = this.getResolvedAsset(dep); if (!resolved) continue; let exported = this.getExportedSymbols(resolved, boundary).filter(s => s.exportSymbol !== 'default').map(s => ({ ...s, exportAs: s.exportSymbol })); symbols.push(...exported); } } return symbols; } getContentHash(bundle) { let existingHash = this._bundleContentHashes.get(bundle.id); if (existingHash != null) { return existingHash; } let hash = new (_hash().Hash)(); // TODO: sort?? this.traverseAssets(bundle, asset => { { hash.writeString([this.getAssetPublicId(asset), asset.outputHash, asset.filePath, asset.query, asset.type, asset.uniqueKey].join(':')); } }); let hashHex = hash.finish(); this._bundleContentHashes.set(bundle.id, hashHex); return hashHex; } getInlineBundles(bundle) { let bundles = []; let seen = new Set(); let addReferencedBundles = bundle => { if (seen.has(bundle.id)) { return; } seen.add(bundle.id); let referencedBundles = this.getReferencedBundles(bundle, { includeInline: true }); for (let referenced of referencedBundles) { if (referenced.bundleBehavior === _types.BundleBehavior.inline) { bundles.push(referenced); addReferencedBundles(referenced); } } }; addReferencedBundles(bundle); this.traverseBundles((childBundle, _, traversal) => { if (childBundle.bundleBehavior === _types.BundleBehavior.inline) { bundles.push(childBundle); } else if (childBundle.id !== bundle.id) { traversal.skipChildren(); } }, bundle); return bundles; } getHash(bundle) { let hash = new (_hash().Hash)(); hash.writeString(bundle.id + bundle.target.publicUrl + this.getContentHash(bundle)); let inlineBundles = this.getInlineBundles(bundle); for (let inlineBundle of inlineBundles) { hash.writeString(this.getContentHash(inlineBundle)); } for (let referencedBundle of this.getReferencedBundles(bundle)) { hash.writeString(referencedBundle.id); } hash.writeString(JSON.stringify((0, _utils().objectSortedEntriesDeep)(bundle.env))); return hash.finish(); } getBundleGraphHash() { let hashes = ''; for (let bundle of this.getBundles()) { hashes += this.getHash(bundle); } return (0, _hash().hashString)(hashes); } addBundleToBundleGroup(bundle, bundleGroup) { let bundleGroupNodeId = this._graph.getNodeIdByContentKey((0, _utils2.getBundleGroupId)(bundleGroup)); let bundleNodeId = this._graph.getNodeIdByContentKey(bundle.id); if (this._graph.hasEdge(bundleGroupNodeId, bundleNodeId, bundleGraphEdgeTypes.bundle)) { // Bundle group already has bundle return; } this._graph.addEdge(bundleGroupNodeId, bundleNodeId); this._graph.addEdge(bundleGroupNodeId, bundleNodeId, bundleGraphEdgeTypes.bundle); for (let entryAssetId of bundle.entryAssetIds) { let entryAssetNodeId = this._graph.getNodeIdByContentKey(entryAssetId); if (this._graph.hasEdge(bundleGroupNodeId, entryAssetNodeId)) { this._graph.removeEdge(bundleGroupNodeId, entryAssetNodeId); } } } getUsedSymbolsAsset(asset) { let node = this._graph.getNodeByContentKey(asset.id); (0, _assert().default)(node && node.type === 'asset'); return this._symbolPropagationRan ? makeReadOnlySet(node.usedSymbols) : null; } getUsedSymbolsDependency(dep) { let node = this._graph.getNodeByContentKey(dep.id); (0, _assert().default)(node && node.type === 'dependency'); return this._symbolPropagationRan ? makeReadOnlySet(node.usedSymbolsUp) : null; } merge(other) { let otherGraphIdToThisNodeId = new Map(); for (let [otherNodeId, otherNode] of other._graph.nodes) { if (this._graph.hasContentKey(otherNode.id)) { let existingNodeId = this._graph.getNodeIdByContentKey(otherNode.id); otherGraphIdToThisNodeId.set(otherNodeId, existingNodeId); let existingNode = (0, _nullthrows().default)(this._graph.getNode(existingNodeId)); // Merge symbols, recompute dep.exluded based on that if (existingNode.type === 'asset') { (0, _assert().default)(otherNode.type === 'asset'); existingNode.usedSymbols = new Set([...existingNode.usedSymbols, ...otherNode.usedSymbols]); } else if (existingNode.type === 'dependency') { (0, _assert().default)(otherNode.type === 'dependency'); existingNode.usedSymbolsDown = new Set([...existingNode.usedSymbolsDown, ...otherNode.usedSymbolsDown]); existingNode.usedSymbolsUp = new Set([...existingNode.usedSymbolsUp, ...otherNode.usedSymbolsUp]); existingNode.excluded = (existingNode.excluded || Boolean(existingNode.hasDeferred)) && (otherNode.excluded || Boolean(otherNode.hasDeferred)); } } else { let updateNodeId = this._graph.addNodeByContentKey(otherNode.id, otherNode); otherGraphIdToThisNodeId.set(otherNodeId, updateNodeId); } } for (let edge of other._graph.getAllEdges()) { this._graph.addEdge((0, _nullthrows().default)(otherGraphIdToThisNodeId.get(edge.from)), (0, _nullthrows().default)(otherGraphIdToThisNodeId.get(edge.to)), edge.type); } } isEntryBundleGroup(bundleGroup) { return this._graph.getNodeIdsConnectedTo((0, _nullthrows().default)(this._graph.getNodeIdByContentKey((0, _utils2.getBundleGroupId)(bundleGroup))), bundleGraphEdgeTypes.bundle).map(id => (0, _nullthrows().default)(this._graph.getNode(id))).some(n => n.type === 'root'); } getEntryRoot(projectRoot, target) { let cached = this._targetEntryRoots.get(target.distDir); if (cached != null) { return cached; } let entryBundleGroupIds = this._graph.getNodeIdsConnectedFrom((0, _nullthrows().default)(this._graph.rootNodeId), bundleGraphEdgeTypes.bundle); let entries = []; for (let bundleGroupId of entryBundleGroupIds) { let bundleGroupNode = this._graph.getNode(bundleGroupId); (0, _assert().default)((bundleGroupNode === null || bundleGroupNode === void 0 ? void 0 : bundleGroupNode.type) === 'bundle_group'); if (bundleGroupNode.value.target.distDir === target.distDir) { let entryAssetNode = this._graph.getNodeByContentKey(bundleGroupNode.value.entryAssetId); (0, _assert().default)((entryAssetNode === null || entryAssetNode === void 0 ? void 0 : entryAssetNode.type) === 'asset'); entries.push((0, _projectPath.fromProjectPath)(projectRoot, entryAssetNode.value.filePath)); } } let root = (0, _utils().getRootDir)(entries); this._targetEntryRoots.set(target.distDir, root); return root; } } exports.default = BundleGraph;