Skip to content

Commit 3ea6c46

Browse files
larixertrivikr
authored andcommitted
fix(nm): improves external soft links hoisting (#4269)
* Improves portal hoisting * Move hasUnhoistedDependencies into a separate function * Updates test, removes debugging code * Adds support and a test for deep nested portals hoisting * Fixes test description * Lint * Update packages/yarnpkg-nm/sources/hoist.ts Co-authored-by: Trivikram Kamat <[email protected]> * Update packages/yarnpkg-nm/sources/hoist.ts Co-authored-by: Trivikram Kamat <[email protected]> * Lint Co-authored-by: Trivikram Kamat <[email protected]>
1 parent e311693 commit 3ea6c46

File tree

4 files changed

+68
-4
lines changed

4 files changed

+68
-4
lines changed

.yarn/versions/567d3c84.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
releases:
2+
"@yarnpkg/cli": patch
3+
"@yarnpkg/nm": patch
4+
"@yarnpkg/plugin-nm": patch
5+
"@yarnpkg/pnpify": patch
6+
7+
declined:
8+
- "@yarnpkg/plugin-compat"
9+
- "@yarnpkg/plugin-constraints"
10+
- "@yarnpkg/plugin-dlx"
11+
- "@yarnpkg/plugin-essentials"
12+
- "@yarnpkg/plugin-init"
13+
- "@yarnpkg/plugin-interactive-tools"
14+
- "@yarnpkg/plugin-npm-cli"
15+
- "@yarnpkg/plugin-pack"
16+
- "@yarnpkg/plugin-patch"
17+
- "@yarnpkg/plugin-pnp"
18+
- "@yarnpkg/plugin-pnpm"
19+
- "@yarnpkg/plugin-stage"
20+
- "@yarnpkg/plugin-typescript"
21+
- "@yarnpkg/plugin-version"
22+
- "@yarnpkg/plugin-workspace-tools"
23+
- "@yarnpkg/builder"
24+
- "@yarnpkg/core"
25+
- "@yarnpkg/doctor"

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Yarn now accepts sponsorships! Please give a look at our [OpenCollective](https:
1111
- The node-modules linker has received various improvements:
1212
- applies hoisting algorithm on aliased dependencies
1313
- reinstalls modules that have their directories removed from node_modules by the user
14+
- improves portal hoisting
1415

1516
**Note:** features in `master` can be tried out by running `yarn set version from sources` in your project (existing contrib plugins are updated automatically, while new contrib plugins can be added by running `yarn plugin import from sources <name>`).
1617

packages/yarnpkg-nm/sources/hoist.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,12 @@ const hoistTo = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>, ro
396396

397397
const hoistIdentMap = getHoistIdentMap(rootNode, preferenceMap);
398398

399-
const usedDependencies = tree == rootNode ? new Map() : (options.fastLookupPossible ? getZeroRoundUsedDependencies(rootNodePath) : getUsedDependencies(rootNodePath));
399+
const usedDependencies = tree == rootNode ? new Map() :
400+
(options.fastLookupPossible
401+
? getZeroRoundUsedDependencies(rootNodePath)
402+
: getUsedDependencies(rootNodePath)
403+
);
404+
400405
let wasStateChanged;
401406

402407
let anotherRoundNeeded = false;
@@ -438,6 +443,15 @@ const hoistTo = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>, ro
438443
return {anotherRoundNeeded, isGraphChanged};
439444
};
440445

446+
const hasUnhoistedDependencies = (node: HoisterWorkTree): boolean => {
447+
for (const [subName, subDependency] of node.dependencies) {
448+
if (!node.peerNames.has(subName) && subDependency.ident !== node.ident) {
449+
return true;
450+
}
451+
}
452+
return false;
453+
};
454+
441455
const getNodeHoistInfo = (rootNode: HoisterWorkTree, rootNodePathLocators: Set<Locator>, nodePath: Array<HoisterWorkTree>, node: HoisterWorkTree, usedDependencies: Map<PackageName, HoisterWorkTree>, hoistIdents: Map<PackageName, Ident>, hoistIdentMap: Map<Ident, Array<Ident>>, shadowedNodes: ShadowedNodes, {outputReason, fastLookupPossible}: {outputReason: boolean, fastLookupPossible: boolean}): HoistInfo => {
442456
let reasonRoot;
443457
let reason: string | null = null;
@@ -459,8 +473,8 @@ const getNodeHoistInfo = (rootNode: HoisterWorkTree, rootNodePathLocators: Set<L
459473
}
460474
}
461475

462-
if (isHoistable) {
463-
isHoistable = node.dependencyKind !== HoisterDependencyKind.EXTERNAL_SOFT_LINK || node.dependencies.size === 0;
476+
if (isHoistable && node.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK) {
477+
isHoistable = !hasUnhoistedDependencies(node);
464478
if (outputReason && !isHoistable) {
465479
reason = `- external soft link with unhoisted dependencies`;
466480
}
@@ -559,7 +573,7 @@ const getNodeHoistInfo = (rootNode: HoisterWorkTree, rootNodePathLocators: Set<L
559573

560574
if (isHoistable && !fastLookupPossible) {
561575
for (const origDep of node.hoistedDependencies.values()) {
562-
const usedDep = usedDependencies.get(origDep.name);
576+
const usedDep = usedDependencies.get(origDep.name) || rootNode.dependencies.get(origDep.name);
563577
if (!usedDep || origDep.ident !== usedDep.ident) {
564578
isHoistable = false;
565579
if (outputReason)
@@ -630,13 +644,15 @@ const hoistGraph = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>,
630644
if (hoistInfo.isHoistable === Hoistable.NO)
631645
addUnhoistableNode(node, hoistInfo, hoistInfo.reason!);
632646

647+
let wereNodesHoisted = false;
633648
for (const node of hoistInfos.keys()) {
634649
if (!unhoistableNodes.has(node)) {
635650
isGraphChanged = true;
636651
const shadowedNames = parentShadowedNodes.get(parentNode);
637652
if (shadowedNames && shadowedNames.has(node.name))
638653
anotherRoundNeeded = true;
639654

655+
wereNodesHoisted = true;
640656
parentNode.dependencies.delete(node.name);
641657
parentNode.hoistedDependencies.set(node.name, node);
642658
parentNode.reasons.delete(node.name);
@@ -668,6 +684,9 @@ const hoistGraph = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>,
668684
}
669685
}
670686

687+
if (parentNode.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK && wereNodesHoisted)
688+
anotherRoundNeeded = true;
689+
671690
if (options.check) {
672691
const checkLog = selfCheck(tree);
673692
if (checkLog) {

packages/yarnpkg-nm/tests/hoist.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,25 @@ describe(`hoist`, () => {
561561
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(3);
562562
});
563563

564+
it(`should not hoist portal with unhoistable dependencies`, () => {
565+
const tree = {
566+
'.': {dependencies: [`P1`, `B@Y`]},
567+
P1: {dependencies: [`P2`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
568+
P2: {dependencies: [`B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
569+
};
570+
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(3);
571+
});
572+
573+
it(`should hoist nested portals with hoisted dependencies`, () => {
574+
const tree = {
575+
'.': {dependencies: [`P1`, `B@X`]},
576+
P1: {dependencies: [`P2`, `B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
577+
P2: {dependencies: [`P3`, `B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
578+
P3: {dependencies: [`B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
579+
};
580+
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(2);
581+
});
582+
564583
it(`should support two branch circular graph hoisting`, () => {
565584
// . -> B -> D@X -> F@X
566585
// -> E@X -> D@X

0 commit comments

Comments
 (0)