To build a dependency tree in memory before persisting it to the filesystem, you can use a recursive function that resolves volatile references (like semver ranges) into pinned references (specific versions) and then fetches their sub-dependencies.
This approach allows for manipulations like deduplication or hoisting to be applied to the in-memory tree rather than the disk, which is significantly faster.
Handling Circular Dependencies
When extracting trees, circular dependencies (e.g., babel-core $\rightarrow$ babel-register $\rightarrow$ babel-core) can cause infinite recursion and memory exhaustion. To prevent this, implement a filtering pass that checks if a dependency is already satisfied by a package available in the upstream dependency chain (the available registry).
If a dependency's reference matches an available reference, or if the available reference satisfies the dependency's semver range, skip the resolution for that branch.
// A robust implementation of dependency tree extraction
async function getPackageDependencyTree(
{ name, reference, dependencies },
available = new Map()
) {
return {
name,
reference,
dependencies: await Promise.all(
dependencies
.filter(volatileDependency => {
let availableReference = available.get(volatileDependency.name);
// Skip if the reference exactly matches an available package
if (volatileDependency.reference === availableReference) return false;
// Skip if the available package satisfies the semver range
if (
semver.validRange(volatileDependency.reference) &&
semver.satisfies(availableReference, volatileDependency.reference)
) {
return false;
}
return true;
})
.map(async volatileDependency => {
let pinnedDependency = await getPinnedReference(volatileDependency);
let subDependencies = await getPackageDependencies(pinnedDependency);
// Pass down the registry of available packages to the next level
let subAvailable = new Map(available);
subAvailable.set(pinnedDependency.name, pinnedDependency.reference);
return await getPackageDependencyTree(
Object.assign({}, pinnedDependency, {
dependencies: subDependencies
}),
subAvailable
);
})
),
};
}