UNPKG

@parcel/core

Version:
427 lines (417 loc) • 18.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AssetGraphBuilder = void 0; exports.default = createAssetGraphRequest; function _logger() { const data = _interopRequireDefault(require("@parcel/logger")); _logger = function () { return data; }; return data; } 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 _utils() { const data = require("@parcel/utils"); _utils = function () { return data; }; return data; } function _rust() { const data = require("@parcel/rust"); _rust = function () { return data; }; return data; } function _diagnostic() { const data = _interopRequireDefault(require("@parcel/diagnostic")); _diagnostic = function () { return data; }; return data; } var _types = require("../types"); var _AssetGraph = _interopRequireDefault(require("../AssetGraph")); var _constants = require("../constants"); var _EntryRequest = _interopRequireDefault(require("./EntryRequest")); var _TargetRequest = _interopRequireDefault(require("./TargetRequest")); var _AssetRequest = _interopRequireDefault(require("./AssetRequest")); var _PathRequest = _interopRequireDefault(require("./PathRequest")); var _projectPath = require("../projectPath"); var _dumpGraphToGraphViz = _interopRequireDefault(require("../dumpGraphToGraphViz")); var _SymbolPropagation = require("../SymbolPropagation"); var _RequestTracker = require("../RequestTracker"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function createAssetGraphRequest(requestInput) { return { type: _RequestTracker.requestTypes.asset_graph_request, id: requestInput.name, run: async input => { let prevResult = await input.api.getPreviousResult(); let builder = new AssetGraphBuilder(input, prevResult); let assetGraphRequest = await await builder.build(); // early break for incremental bundling if production or flag is off; if (!input.options.shouldBundleIncrementally || input.options.mode === 'production') { assetGraphRequest.assetGraph.safeToIncrementallyBundle = false; } return assetGraphRequest; }, input: requestInput }; } const typesWithRequests = new Set(['entry_specifier', 'entry_file', 'dependency', 'asset_group']); class AssetGraphBuilder { assetRequests = []; constructor({ input, api, options }, prevResult) { let { entries, assetGroups, optionsRef, name, requestedAssetIds, shouldBuildLazily, lazyIncludes, lazyExcludes } = input; let assetGraph = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.assetGraph) ?? new _AssetGraph.default(); assetGraph.safeToIncrementallyBundle = true; assetGraph.setRootConnections({ entries, assetGroups }); assetGraph.undeferredDependencies.clear(); this.assetGroupsWithRemovedParents = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.assetGroupsWithRemovedParents) ?? new Set(); this.previousSymbolPropagationErrors = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.previousSymbolPropagationErrors) ?? new Map(); this.changedAssets = (prevResult === null || prevResult === void 0 ? void 0 : prevResult.changedAssets) ?? new Map(); this.changedAssetsPropagation = new Set(); this.prevChangedAssetsPropagation = prevResult === null || prevResult === void 0 ? void 0 : prevResult.changedAssetsPropagation; this.assetGraph = assetGraph; this.optionsRef = optionsRef; this.options = options; this.api = api; this.name = name; this.requestedAssetIds = requestedAssetIds ?? new Set(); this.shouldBuildLazily = shouldBuildLazily ?? false; this.lazyIncludes = lazyIncludes ?? []; this.lazyExcludes = lazyExcludes ?? []; this.cacheKey = (0, _rust().hashString)(`${_constants.PARCEL_VERSION}${name}${JSON.stringify(entries) ?? ''}${options.mode}${options.shouldBuildLazily ? 'lazy' : 'eager'}`) + '-AssetGraph'; this.isSingleChangeRebuild = api.getInvalidSubRequests().filter(req => req.requestType === 'asset_request').length === 1; this.queue = new (_utils().PromiseQueue)(); assetGraph.onNodeRemoved = nodeId => { this.assetGroupsWithRemovedParents.delete(nodeId); // This needs to mark all connected nodes that doesn't become orphaned // due to replaceNodesConnectedTo to make sure that the symbols of // nodes from which at least one parent was removed are updated. let node = (0, _nullthrows().default)(assetGraph.getNode(nodeId)); if (assetGraph.isOrphanedNode(nodeId) && node.type === 'dependency') { let children = assetGraph.getNodeIdsConnectedFrom(nodeId); for (let child of children) { let childNode = (0, _nullthrows().default)(assetGraph.getNode(child)); (0, _assert().default)(childNode.type === 'asset_group' || childNode.type === 'asset'); childNode.usedSymbolsDownDirty = true; this.assetGroupsWithRemovedParents.add(child); } } }; } async build() { let errors = []; let rootNodeId = (0, _nullthrows().default)(this.assetGraph.rootNodeId, 'A root node is required to traverse'); let visitedAssetGroups = new Set(); let visited = new Set([rootNodeId]); const visit = nodeId => { if (errors.length > 0) { return; } if (this.shouldSkipRequest(nodeId)) { visitChildren(nodeId); } else { // ? do we need to visit children inside of the promise that is queued? this.queueCorrespondingRequest(nodeId, errors).then(() => visitChildren(nodeId)); } }; const visitChildren = nodeId => { for (let childNodeId of this.assetGraph.getNodeIdsConnectedFrom(nodeId)) { let child = (0, _nullthrows().default)(this.assetGraph.getNode(childNodeId)); if ((!visited.has(childNodeId) || child.hasDeferred) && this.shouldVisitChild(nodeId, childNodeId)) { if (child.type === 'asset_group') { visitedAssetGroups.add(childNodeId); } visited.add(childNodeId); visit(childNodeId); } } }; visit(rootNodeId); await this.queue.run(); _logger().default.verbose({ origin: '@parcel/core', message: 'Asset graph walked', meta: { visitedAssetGroupsCount: visitedAssetGroups.size } }); if (this.prevChangedAssetsPropagation) { // Add any previously seen Assets that have not been propagated yet to // 'this.changedAssetsPropagation', but only if they still remain in the graph // as they could have been removed since the last build for (let assetId of this.prevChangedAssetsPropagation) { if (this.assetGraph.hasContentKey(assetId)) { this.changedAssetsPropagation.add(assetId); } } } if (errors.length) { this.api.storeResult({ assetGraph: this.assetGraph, changedAssets: this.changedAssets, changedAssetsPropagation: this.changedAssetsPropagation, assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents, previousSymbolPropagationErrors: undefined, assetRequests: [] }, this.cacheKey); // TODO: eventually support multiple errors since requests could reject in parallel throw errors[0]; } if (this.assetGraph.nodes.length > 1) { await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name + '_before_prop'); try { let errors = (0, _SymbolPropagation.propagateSymbols)({ options: this.options, assetGraph: this.assetGraph, changedAssetsPropagation: this.changedAssetsPropagation, assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents, previousErrors: this.previousSymbolPropagationErrors }); this.changedAssetsPropagation.clear(); if (errors.size > 0) { this.api.storeResult({ assetGraph: this.assetGraph, changedAssets: this.changedAssets, changedAssetsPropagation: this.changedAssetsPropagation, assetGroupsWithRemovedParents: this.assetGroupsWithRemovedParents, previousSymbolPropagationErrors: errors, assetRequests: [] }, this.cacheKey); // Just throw the first error. Since errors can bubble (e.g. reexporting a reexported symbol also fails), // determining which failing export is the root cause is nontrivial (because of circular dependencies). throw new (_diagnostic().default)({ diagnostic: [...errors.values()][0] }); } } catch (e) { await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name + '_failed'); throw e; } } await (0, _dumpGraphToGraphViz.default)(this.assetGraph, 'AssetGraph_' + this.name); this.api.storeResult({ assetGraph: this.assetGraph, changedAssets: new Map(), changedAssetsPropagation: this.changedAssetsPropagation, assetGroupsWithRemovedParents: undefined, previousSymbolPropagationErrors: undefined, assetRequests: [] }, this.cacheKey); return { assetGraph: this.assetGraph, changedAssets: this.changedAssets, changedAssetsPropagation: this.changedAssetsPropagation, assetGroupsWithRemovedParents: undefined, previousSymbolPropagationErrors: undefined, assetRequests: this.assetRequests }; } shouldVisitChild(nodeId, childNodeId) { if (this.shouldBuildLazily) { let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId)); let childNode = (0, _nullthrows().default)(this.assetGraph.getNode(childNodeId)); if (node.type === 'asset' && childNode.type === 'dependency') { // This logic will set `node.requested` to `true` if the node is in the list of requested asset ids // (i.e. this is an entry of a (probably) placeholder bundle that wasn't previously requested) // // Otherwise, if this node either is explicitly not requested, or has had it's requested attribute deleted, // it will determine whether this node is an "async child" - that is, is it a (probably) // dynamic import(). If so, it will explicitly have it's `node.requested` set to `false` // // If it's not requested, but it's not an async child then it's `node.requested` is deleted (undefined) // by default with lazy compilation all nodes are lazy let isNodeLazy = true; // For conditional lazy building - if this node matches the `lazyInclude` globs that means we want // only those nodes to be treated as lazy - that means if this node does _NOT_ match that glob, then we // also consider it not lazy (so it gets marked as requested). const relativePath = (0, _projectPath.fromProjectPathRelative)(node.value.filePath); if (this.lazyIncludes.length > 0) { isNodeLazy = this.lazyIncludes.some(lazyIncludeRegex => relativePath.match(lazyIncludeRegex)); } // Excludes override includes, so a node is _not_ lazy if it is included in the exclude list. if (this.lazyExcludes.length > 0 && isNodeLazy) { isNodeLazy = !this.lazyExcludes.some(lazyExcludeRegex => relativePath.match(lazyExcludeRegex)); } if (this.requestedAssetIds.has(node.value.id) || !isNodeLazy) { node.requested = true; } else if (!node.requested) { let isAsyncChild = this.assetGraph.getIncomingDependencies(node.value).every(dep => dep.isEntry || dep.priority !== _types.Priority.sync); if (isAsyncChild) { node.requested = !isNodeLazy; } else { delete node.requested; } } let previouslyDeferred = childNode.deferred; childNode.deferred = node.requested === false; // The child dependency node we're now evaluating should not be deferred if it's parent // is explicitly not requested (requested = false, but not requested = undefined) // // if we weren't previously deferred but we are now, then this dependency node's parents should also // be marked as deferred // // if we were previously deferred but we not longer are, then then all parents should no longer be // deferred either if (!previouslyDeferred && childNode.deferred) { this.assetGraph.markParentsWithHasDeferred(childNodeId); } else if (previouslyDeferred && !childNode.deferred) { // Mark Asset and Dependency as dirty for symbol propagation as it was // previously deferred and it's used symbols may have changed this.changedAssetsPropagation.add(node.id); node.usedSymbolsDownDirty = true; this.changedAssetsPropagation.add(childNode.id); childNode.usedSymbolsDownDirty = true; this.assetGraph.unmarkParentsWithHasDeferred(childNodeId); } // We `shouldVisitChild` if the childNode is not deferred return !childNode.deferred; } } return this.assetGraph.shouldVisitChild(nodeId, childNodeId); } shouldSkipRequest(nodeId) { let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId)); return node.complete === true || !typesWithRequests.has(node.type) || node.correspondingRequest != null && this.api.canSkipSubrequest(node.correspondingRequest); } queueCorrespondingRequest(nodeId, errors) { let promise; let node = (0, _nullthrows().default)(this.assetGraph.getNode(nodeId)); switch (node.type) { case 'entry_specifier': promise = this.runEntryRequest(node.value); break; case 'entry_file': promise = this.runTargetRequest(node.value); break; case 'dependency': promise = this.runPathRequest(node.value); break; case 'asset_group': promise = this.runAssetRequest(node.value); break; default: throw new Error(`Can not queue corresponding request of node with type ${node.type}`); } return this.queue.add(() => promise.then(null, error => errors.push(error))); } async runEntryRequest(input) { let prevEntries = this.assetGraph.safeToIncrementallyBundle ? this.assetGraph.getEntryAssets().map(asset => asset.id).sort() : []; let request = (0, _EntryRequest.default)(input); let result = await this.api.runRequest(request, { force: true }); this.assetGraph.resolveEntry(request.input, result.entries, request.id); if (this.assetGraph.safeToIncrementallyBundle) { let currentEntries = this.assetGraph.getEntryAssets().map(asset => asset.id).sort(); let didEntriesChange = prevEntries.length !== currentEntries.length || prevEntries.every((entryId, index) => entryId === currentEntries[index]); if (didEntriesChange) { this.assetGraph.safeToIncrementallyBundle = false; } } } async runTargetRequest(input) { let request = (0, _TargetRequest.default)(input); let targets = await this.api.runRequest(request, { force: true }); this.assetGraph.resolveTargets(request.input, targets, request.id); } async runPathRequest(input) { let request = (0, _PathRequest.default)({ dependency: input, name: this.name }); let result = await this.api.runRequest(request, { force: true }); this.assetGraph.resolveDependency(input, result, request.id); } async runAssetRequest(input) { this.assetRequests.push(input); let request = (0, _AssetRequest.default)({ ...input, name: this.name, optionsRef: this.optionsRef, isSingleChangeRebuild: this.isSingleChangeRebuild }); let assets = await this.api.runRequest(request, { force: true }); if (assets != null) { for (let asset of assets) { if (this.assetGraph.safeToIncrementallyBundle) { let otherAsset = this.assetGraph.getNodeByContentKey(asset.id); if (otherAsset != null) { (0, _assert().default)(otherAsset.type === 'asset'); if (!this._areDependenciesEqualForAssets(asset, otherAsset.value)) { this.assetGraph.safeToIncrementallyBundle = false; } } else { // adding a new entry or dependency this.assetGraph.safeToIncrementallyBundle = false; } } this.changedAssets.set(asset.id, asset); this.changedAssetsPropagation.add(asset.id); } this.assetGraph.resolveAssetGroup(input, assets, request.id); } else { this.assetGraph.safeToIncrementallyBundle = false; } this.isSingleChangeRebuild = false; } /** * Used for incremental bundling of modified assets */ _areDependenciesEqualForAssets(asset, otherAsset) { let assetDependencies = Array.from(asset === null || asset === void 0 ? void 0 : asset.dependencies.keys()).sort(); let otherAssetDependencies = Array.from(otherAsset === null || otherAsset === void 0 ? void 0 : otherAsset.dependencies.keys()).sort(); if (assetDependencies.length !== otherAssetDependencies.length) { return false; } return assetDependencies.every((key, index) => { var _asset$dependencies$g, _otherAsset$dependenc; if (key !== otherAssetDependencies[index]) { return false; } return (0, _utils().setEqual)(new Set(asset === null || asset === void 0 || (_asset$dependencies$g = asset.dependencies.get(key)) === null || _asset$dependencies$g === void 0 || (_asset$dependencies$g = _asset$dependencies$g.symbols) === null || _asset$dependencies$g === void 0 ? void 0 : _asset$dependencies$g.keys()), new Set(otherAsset === null || otherAsset === void 0 || (_otherAsset$dependenc = otherAsset.dependencies.get(key)) === null || _otherAsset$dependenc === void 0 || (_otherAsset$dependenc = _otherAsset$dependenc.symbols) === null || _otherAsset$dependenc === void 0 ? void 0 : _otherAsset$dependenc.keys())); }); } } exports.AssetGraphBuilder = AssetGraphBuilder;