@parcel/core
Version:
427 lines (417 loc) • 18.5 kB
JavaScript
"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;