Skip to content

Commit

Permalink
fix(nm): improves external soft links hoisting (#4269)
Browse files Browse the repository at this point in the history
* 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 <16024985+trivikr@users.noreply.github.com>

* Update packages/yarnpkg-nm/sources/hoist.ts

Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>

* Lint

Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
  • Loading branch information
larixer and trivikr committed Mar 28, 2022
1 parent 9a5499c commit 7f64a98
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 4 deletions.
25 changes: 25 additions & 0 deletions .yarn/versions/567d3c84.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
releases:
"@yarnpkg/cli": patch
"@yarnpkg/nm": patch
"@yarnpkg/plugin-nm": patch
"@yarnpkg/pnpify": patch

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Yarn now accepts sponsorships! Please give a look at our [OpenCollective](https:
- The node-modules linker has received various improvements:
- applies hoisting algorithm on aliased dependencies
- reinstalls modules that have their directories removed from node_modules by the user
- improves portal hoisting

**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>`).

Expand Down
27 changes: 23 additions & 4 deletions packages/yarnpkg-nm/sources/hoist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,12 @@ const hoistTo = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>, ro

const hoistIdentMap = getHoistIdentMap(rootNode, preferenceMap);

const usedDependencies = tree == rootNode ? new Map() : (options.fastLookupPossible ? getZeroRoundUsedDependencies(rootNodePath) : getUsedDependencies(rootNodePath));
const usedDependencies = tree == rootNode ? new Map() :
(options.fastLookupPossible
? getZeroRoundUsedDependencies(rootNodePath)
: getUsedDependencies(rootNodePath)
);

let wasStateChanged;

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

const hasUnhoistedDependencies = (node: HoisterWorkTree): boolean => {
for (const [subName, subDependency] of node.dependencies) {
if (!node.peerNames.has(subName) && subDependency.ident !== node.ident) {
return true;
}
}
return false;
};

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 => {
let reasonRoot;
let reason: string | null = null;
Expand All @@ -459,8 +473,8 @@ const getNodeHoistInfo = (rootNode: HoisterWorkTree, rootNodePathLocators: Set<L
}
}

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

if (isHoistable && !fastLookupPossible) {
for (const origDep of node.hoistedDependencies.values()) {
const usedDep = usedDependencies.get(origDep.name);
const usedDep = usedDependencies.get(origDep.name) || rootNode.dependencies.get(origDep.name);
if (!usedDep || origDep.ident !== usedDep.ident) {
isHoistable = false;
if (outputReason)
Expand Down Expand Up @@ -630,13 +644,15 @@ const hoistGraph = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>,
if (hoistInfo.isHoistable === Hoistable.NO)
addUnhoistableNode(node, hoistInfo, hoistInfo.reason!);

let wereNodesHoisted = false;
for (const node of hoistInfos.keys()) {
if (!unhoistableNodes.has(node)) {
isGraphChanged = true;
const shadowedNames = parentShadowedNodes.get(parentNode);
if (shadowedNames && shadowedNames.has(node.name))
anotherRoundNeeded = true;

wereNodesHoisted = true;
parentNode.dependencies.delete(node.name);
parentNode.hoistedDependencies.set(node.name, node);
parentNode.reasons.delete(node.name);
Expand Down Expand Up @@ -668,6 +684,9 @@ const hoistGraph = (tree: HoisterWorkTree, rootNodePath: Array<HoisterWorkTree>,
}
}

if (parentNode.dependencyKind === HoisterDependencyKind.EXTERNAL_SOFT_LINK && wereNodesHoisted)
anotherRoundNeeded = true;

if (options.check) {
const checkLog = selfCheck(tree);
if (checkLog) {
Expand Down
19 changes: 19 additions & 0 deletions packages/yarnpkg-nm/tests/hoist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,25 @@ describe(`hoist`, () => {
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(3);
});

it(`should not hoist portal with unhoistable dependencies`, () => {
const tree = {
'.': {dependencies: [`P1`, `B@Y`]},
P1: {dependencies: [`P2`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
P2: {dependencies: [`B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
};
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(3);
});

it(`should hoist nested portals with hoisted dependencies`, () => {
const tree = {
'.': {dependencies: [`P1`, `B@X`]},
P1: {dependencies: [`P2`, `B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
P2: {dependencies: [`P3`, `B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
P3: {dependencies: [`B@X`], dependencyKind: HoisterDependencyKind.EXTERNAL_SOFT_LINK},
};
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(2);
});

it(`should support two branch circular graph hoisting`, () => {
// . -> B -> D@X -> F@X
// -> E@X -> D@X
Expand Down

0 comments on commit 7f64a98

Please sign in to comment.