UNPKG

@parcel/core

Version:
526 lines (516 loc) • 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; exports.nodeFromAsset = nodeFromAsset; exports.nodeFromAssetGroup = nodeFromAssetGroup; exports.nodeFromDep = nodeFromDep; exports.nodeFromEntryFile = nodeFromEntryFile; exports.nodeFromEntrySpecifier = nodeFromEntrySpecifier; function _assert() { const data = _interopRequireDefault(require("assert")); _assert = 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; } 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; } var _Dependency = require("./Dependency"); var _projectPath = require("./projectPath"); var _Environment = require("./public/Environment"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function nodeFromDep(dep) { return { id: dep.id, type: 'dependency', value: dep, deferred: false, excluded: false, usedSymbolsDown: new Set(), usedSymbolsUp: new Map(), usedSymbolsDownDirty: true, usedSymbolsUpDirtyDown: true, usedSymbolsUpDirtyUp: true }; } function nodeFromAssetGroup(assetGroup) { return { id: (0, _rust().hashString)((0, _projectPath.fromProjectPathRelative)(assetGroup.filePath) + assetGroup.env.id + String(assetGroup.isSource) + String(assetGroup.sideEffects) + (assetGroup.code ?? '') + ':' + (assetGroup.pipeline ?? '') + ':' + (assetGroup.query ?? '')), type: 'asset_group', value: assetGroup, usedSymbolsDownDirty: true }; } function nodeFromAsset(asset) { return { id: asset.id, type: 'asset', value: asset, usedSymbols: new Set(), usedSymbolsDownDirty: true, usedSymbolsUpDirty: true }; } function nodeFromEntrySpecifier(entry) { return { id: 'entry_specifier:' + (0, _projectPath.fromProjectPathRelative)(entry), type: 'entry_specifier', value: entry }; } function nodeFromEntryFile(entry) { return { id: 'entry_file:' + (0, _utils().hashObject)(entry), type: 'entry_file', value: entry }; } class AssetGraph extends _graph().ContentGraph { safeToIncrementallyBundle = true; constructor(opts) { if (opts) { let { hash, ...rest } = opts; super(rest); this.hash = hash; } else { super(); this.setRootNodeId(this.addNode({ id: '@@root', type: 'root', value: null })); } this.undeferredDependencies = new Set(); this.envCache = new Map(); } // $FlowFixMe[prop-missing] static deserialize(opts) { return new AssetGraph(opts); } // $FlowFixMe[prop-missing] serialize() { return { ...super.serialize(), hash: this.hash }; } // Deduplicates Environments by making them referentially equal normalizeEnvironment(input) { let { id, context } = input.env; let idAndContext = `${id}-${context}`; let env = this.envCache.get(idAndContext); if (env) { input.env = env; } else { this.envCache.set(idAndContext, input.env); } } setRootConnections({ entries, assetGroups }) { let nodes = []; if (entries) { for (let entry of entries) { let node = nodeFromEntrySpecifier(entry); nodes.push(node); } } else if (assetGroups) { nodes.push(...assetGroups.map(assetGroup => nodeFromAssetGroup(assetGroup))); } this.replaceNodeIdsConnectedTo((0, _nullthrows().default)(this.rootNodeId), nodes.map(node => this.addNode(node))); } addNode(node) { this.hash = null; let existing = this.getNodeByContentKey(node.id); if (existing != null) { (0, _assert().default)(existing.type === node.type); // $FlowFixMe[incompatible-type] Checked above // $FlowFixMe[prop-missing] existing.value = node.value; let existingId = this.getNodeIdByContentKey(node.id); this.updateNode(existingId, existing); return existingId; } return super.addNodeByContentKey(node.id, node); } removeNode(nodeId) { this.hash = null; this.onNodeRemoved && this.onNodeRemoved(nodeId); return super.removeNode(nodeId); } resolveEntry(entry, resolved, correspondingRequest) { let entrySpecifierNodeId = this.getNodeIdByContentKey(nodeFromEntrySpecifier(entry).id); let entrySpecifierNode = (0, _nullthrows().default)(this.getNode(entrySpecifierNodeId)); (0, _assert().default)(entrySpecifierNode.type === 'entry_specifier'); entrySpecifierNode.correspondingRequest = correspondingRequest; this.replaceNodeIdsConnectedTo(entrySpecifierNodeId, resolved.map(file => this.addNode(nodeFromEntryFile(file)))); } resolveTargets(entry, targets, correspondingRequest) { let depNodes = targets.map(target => { // In library mode, all of the entry's symbols are "used" // In non-browser environments, exports may also be used (e.g. serverless request handlers). let includeAllSymbols = target.env.isLibrary || !_Environment.BROWSER_ENVS.has(target.env.context); let node = nodeFromDep( // The passed project path is ignored in this case, because there is no `loc` (0, _Dependency.createDependency)('', { specifier: (0, _projectPath.fromProjectPathRelative)(entry.filePath), specifierType: 'esm', // ??? pipeline: target.pipeline, target: target, env: target.env, isEntry: true, needsStableName: true, symbols: includeAllSymbols ? new Map([['*', { local: '*', isWeak: true, loc: null }]]) : undefined })); if (includeAllSymbols) { node.usedSymbolsDown.add('*'); node.usedSymbolsUp.set('*', undefined); } return node; }); let entryNodeId = this.getNodeIdByContentKey(nodeFromEntryFile(entry).id); let entryNode = (0, _nullthrows().default)(this.getNode(entryNodeId)); (0, _assert().default)(entryNode.type === 'entry_file'); entryNode.correspondingRequest = correspondingRequest; this.replaceNodeIdsConnectedTo(entryNodeId, depNodes.map(node => this.addNode(node))); } resolveDependency(dependency, assetGroup, correspondingRequest) { let depNodeId = this.getNodeIdByContentKey(dependency.id); let depNode = (0, _nullthrows().default)(this.getNode(depNodeId)); (0, _assert().default)(depNode.type === 'dependency'); depNode.correspondingRequest = correspondingRequest; if (!assetGroup) { return; } let assetGroupNode = nodeFromAssetGroup(assetGroup); let existing = this.getNodeByContentKey(assetGroupNode.id); if (existing != null) { (0, _assert().default)(existing.type === 'asset_group'); assetGroupNode.value.canDefer = assetGroupNode.value.canDefer && existing.value.canDefer; } let assetGroupNodeId = this.addNode(assetGroupNode); this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(dependency.id), [assetGroupNodeId]); this.replaceNodeIdsConnectedTo(depNodeId, [assetGroupNodeId]); } shouldVisitChild(nodeId, childNodeId) { let node = (0, _nullthrows().default)(this.getNode(nodeId)); let childNode = (0, _nullthrows().default)(this.getNode(childNodeId)); if (node.type !== 'dependency' || childNode.type !== 'asset_group' || childNode.deferred === false) { return true; } // Node types are proved above let dependencyNode = node; let assetGroupNode = childNode; let { sideEffects, canDefer = true } = assetGroupNode.value; let dependency = dependencyNode.value; let dependencyPreviouslyDeferred = dependencyNode.hasDeferred; let assetGroupPreviouslyDeferred = assetGroupNode.deferred; let defer = this.shouldDeferDependency(dependency, sideEffects, canDefer); dependencyNode.hasDeferred = defer; assetGroupNode.deferred = defer; if (!dependencyPreviouslyDeferred && defer) { this.markParentsWithHasDeferred(nodeId); } else if (assetGroupPreviouslyDeferred && !defer) { this.unmarkParentsWithHasDeferred(childNodeId); } return !defer; } // Dependency: mark parent Asset <- AssetGroup with hasDeferred true markParentsWithHasDeferred(nodeId) { this.traverseAncestors(nodeId, (traversedNodeId, _, actions) => { let traversedNode = (0, _nullthrows().default)(this.getNode(traversedNodeId)); if (traversedNode.type === 'asset') { traversedNode.hasDeferred = true; } else if (traversedNode.type === 'asset_group') { traversedNode.hasDeferred = true; actions.skipChildren(); } else if (nodeId !== traversedNodeId) { actions.skipChildren(); } }); } // AssetGroup: update hasDeferred of all parent Dependency <- Asset <- AssetGroup unmarkParentsWithHasDeferred(nodeId) { this.traverseAncestors(nodeId, (traversedNodeId, ctx, actions) => { let traversedNode = (0, _nullthrows().default)(this.getNode(traversedNodeId)); if (traversedNode.type === 'asset') { let hasDeferred = this.getNodeIdsConnectedFrom(traversedNodeId).some(childNodeId => { let childNode = (0, _nullthrows().default)(this.getNode(childNodeId)); return childNode.hasDeferred == null ? false : childNode.hasDeferred; }); if (!hasDeferred) { delete traversedNode.hasDeferred; } return { hasDeferred }; } else if (traversedNode.type === 'asset_group' && nodeId !== traversedNodeId) { if (!(ctx !== null && ctx !== void 0 && ctx.hasDeferred)) { this.safeToIncrementallyBundle = false; delete traversedNode.hasDeferred; } actions.skipChildren(); } else if (traversedNode.type === 'dependency') { this.safeToIncrementallyBundle = false; traversedNode.hasDeferred = false; } else if (nodeId !== traversedNodeId) { actions.skipChildren(); } }); } // Defer transforming this dependency if it is marked as weak, there are no side effects, // no re-exported symbols are used by ancestor dependencies and the re-exporting asset isn't // using a wildcard and isn't an entry (in library mode). // This helps with performance building large libraries like `lodash-es`, which re-exports // a huge number of functions since we can avoid even transforming the files that aren't used. shouldDeferDependency(dependency, sideEffects, canDefer) { let dependencySymbols = dependency.symbols; // Doing this separately keeps Flow happy further down if (!dependencySymbols) { return false; } let isDeferrable = [...dependencySymbols].every(([, { isWeak }]) => isWeak) && sideEffects === false && canDefer && !dependencySymbols.has('*'); if (!isDeferrable) { return false; } let depNodeId = this.getNodeIdByContentKey(dependency.id); let depNode = this.getNode(depNodeId); (0, _assert().default)(depNode); let assets = this.getNodeIdsConnectedTo(depNodeId); let symbols = new Map([...dependencySymbols].map(([key, val]) => [val.local, key])); (0, _assert().default)(assets.length === 1); let firstAsset = (0, _nullthrows().default)(this.getNode(assets[0])); (0, _assert().default)(firstAsset.type === 'asset'); let resolvedAsset = firstAsset.value; // This doesn't change from here, so checking it now saves // us some calls to `getIncomingDependency` if (!resolvedAsset.symbols) { return true; } let deps = this.getIncomingDependencies(resolvedAsset); return deps.every(d => { // If this dependency has already been through this process, and we // know it's not deferrable, then there's no need to re-check if (this.undeferredDependencies.has(d)) { return false; } let depIsDeferrable = d.symbols && !(d.env.isLibrary && d.isEntry) && !d.symbols.has('*') && ![...d.symbols.keys()].some(symbol => { var _resolvedAsset$symbol; let assetSymbol = (_resolvedAsset$symbol = resolvedAsset.symbols) === null || _resolvedAsset$symbol === void 0 || (_resolvedAsset$symbol = _resolvedAsset$symbol.get(symbol)) === null || _resolvedAsset$symbol === void 0 ? void 0 : _resolvedAsset$symbol.local; return assetSymbol != null && symbols.has(assetSymbol); }); if (!depIsDeferrable) { // Mark this dep as not deferrable so it doesn't have to be re-checked this.undeferredDependencies.add(d); return false; } }); } resolveAssetGroup(assetGroup, assets, correspondingRequest) { this.normalizeEnvironment(assetGroup); let assetGroupNode = nodeFromAssetGroup(assetGroup); assetGroupNode = this.getNodeByContentKey(assetGroupNode.id); if (!assetGroupNode) { return; } (0, _assert().default)(assetGroupNode.type === 'asset_group'); assetGroupNode.correspondingRequest = correspondingRequest; let assetsByKey = new Map(); for (let asset of assets) { if (asset.uniqueKey != null) { assetsByKey.set(asset.uniqueKey, asset); } } let dependentAssetKeys = new Set(); for (let asset of assets) { for (let dep of asset.dependencies.values()) { if (assetsByKey.has(dep.specifier)) { dependentAssetKeys.add(dep.specifier); } } } let assetObjects = []; let assetNodeIds = []; for (let asset of assets) { this.normalizeEnvironment(asset); let isDirect = !dependentAssetKeys.has(asset.uniqueKey); let dependentAssets = []; for (let dep of asset.dependencies.values()) { let dependentAsset = assetsByKey.get(dep.specifier); if (dependentAsset) { dependentAssets.push(dependentAsset); if (dependentAsset.id === asset.id) { // Don't orphan circular dependencies. isDirect = true; } } } let id = this.addNode(nodeFromAsset(asset)); assetObjects.push({ assetNodeId: id, dependentAssets }); if (isDirect) { assetNodeIds.push(id); } } this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(assetGroupNode.id), assetNodeIds); for (let { assetNodeId, dependentAssets } of assetObjects) { // replaceNodesConnectedTo has merged the value into the existing node, retrieve // the actual current node. let assetNode = (0, _nullthrows().default)(this.getNode(assetNodeId)); (0, _assert().default)(assetNode.type === 'asset'); this.resolveAsset(assetNode, dependentAssets); } } resolveAsset(assetNode, dependentAssets) { let depNodeIds = []; let depNodesWithAssets = []; for (let dep of assetNode.value.dependencies.values()) { this.normalizeEnvironment(dep); let depNode = nodeFromDep(dep); let existing = this.getNodeByContentKey(depNode.id); if ((existing === null || existing === void 0 ? void 0 : existing.type) === 'dependency' && existing.value.resolverMeta != null) { depNode.value.meta = { ...depNode.value.meta, ...existing.value.resolverMeta }; depNode.value.resolverMeta = existing.value.resolverMeta; } if ((existing === null || existing === void 0 ? void 0 : existing.type) === 'dependency' && existing.value.resolverPriority != null) { depNode.value.priority = existing.value.resolverPriority; depNode.value.resolverPriority = existing.value.resolverPriority; } let dependentAsset = dependentAssets.find(a => a.uniqueKey === dep.specifier); if (dependentAsset) { depNode.complete = true; depNodesWithAssets.push([depNode, nodeFromAsset(dependentAsset)]); } depNode.value.sourceAssetType = assetNode.value.type; depNodeIds.push(this.addNode(depNode)); } assetNode.usedSymbolsUpDirty = true; assetNode.usedSymbolsDownDirty = true; this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(assetNode.id), depNodeIds); for (let [depNode, dependentAssetNode] of depNodesWithAssets) { let depAssetNodeId = this.addNode(dependentAssetNode); this.replaceNodeIdsConnectedTo(this.getNodeIdByContentKey(depNode.id), [depAssetNodeId]); } } getIncomingDependencies(asset) { let nodeId = this.getNodeIdByContentKey(asset.id); let assetGroupIds = this.getNodeIdsConnectedTo(nodeId); let dependencies = []; for (let i = 0; i < assetGroupIds.length; i++) { let assetGroupId = assetGroupIds[i]; // Sometimes assets are connected directly to dependencies // rather than through an asset group. This happens due to // inline dependencies on assets via uniqueKey. See resolveAsset. let node = this.getNode(assetGroupId); if ((node === null || node === void 0 ? void 0 : node.type) === 'dependency') { dependencies.push(node.value); continue; } let assetIds = this.getNodeIdsConnectedTo(assetGroupId); for (let j = 0; j < assetIds.length; j++) { let node = this.getNode(assetIds[j]); if (!node || node.type !== 'dependency') { continue; } dependencies.push(node.value); } } return dependencies; } traverseAssets(visit, startNodeId) { return this.filteredTraverse(nodeId => { let node = (0, _nullthrows().default)(this.getNode(nodeId)); return node.type === 'asset' ? node.value : null; }, visit, startNodeId); } getEntryAssetGroupNodes() { let entryNodes = []; this.traverse((nodeId, _, actions) => { let node = (0, _nullthrows().default)(this.getNode(nodeId)); if (node.type === 'asset_group') { entryNodes.push(node); actions.skipChildren(); } }); return entryNodes; } getEntryAssets() { let entries = []; this.traverseAssets((asset, ctx, traversal) => { entries.push(asset); traversal.skipChildren(); }); return entries; } getHash() { if (this.hash != null) { return this.hash; } let hash = new (_rust().Hash)(); // TODO: sort?? this.traverse(nodeId => { let node = (0, _nullthrows().default)(this.getNode(nodeId)); if (node.type === 'asset') { hash.writeString((0, _nullthrows().default)(node.value.outputHash)); } else if (node.type === 'dependency' && node.value.target) { hash.writeString(JSON.stringify(node.value.target)); } }); this.hash = hash.finish(); return this.hash; } } exports.default = AssetGraph;