Skip to content

Commit a22486f

Browse files
authored
fix(nm): Stop hoisting rounds only when nothing were hoisted (#6495)
## What's the problem this PR addresses? <!-- Describe the rationale of your PR. --> <!-- Link all issues that it closes. (Closes/Resolves #xxxx.) --> Fixes #6493 Fixes #6494 ## How did you fix it? <!-- A detailed description of your implementation. --> Issue: #6493. Sometimes hoisting algorithm was stopped too early, because it wasn't able to determine correctly stop condition. I made it safer but possibly one round slower by stopping only when nothing was hoisted during the last round. Issue: #6494. Hoisting avoided hoisting to non-root workspace previously, I have removed this limitation, because it made inner workspaces meaningless. ## Checklist <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed.
1 parent 10d16c3 commit a22486f

File tree

4 files changed

+64
-24
lines changed

4 files changed

+64
-24
lines changed

.yarn/versions/40a42318.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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Features in `master` can be tried out by running `yarn set version from sources`
1010

1111
- Fixes `preferInteractive` forcing interactive mode in non-TTY environments.
1212
- `node-modules` linker now honors user-defined symlinks for `<workspace>/node_modules` directories
13+
- `node-modules` linker supports hoisting into inner workspaces that are parents of other workspaces
14+
- `node-modules` linker attemps to hoist tree more exhaustivel until nothing can be hoisted
1315

1416
## 4.1.0
1517

packages/yarnpkg-nm/sources/hoist.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ export const hoist = (tree: HoisterTree, opts: HoistOptions = {}): HoisterResult
132132
let anotherRoundNeeded = false;
133133
let round = 0;
134134
do {
135-
anotherRoundNeeded = hoistTo(treeCopy, [treeCopy], new Set([treeCopy.locator]), new Map(), options).anotherRoundNeeded;
135+
const result = hoistTo(treeCopy, [treeCopy], new Set([treeCopy.locator]), new Map(), options);
136+
anotherRoundNeeded = result.anotherRoundNeeded || result.isGraphChanged;
136137
options.fastLookupPossible = false;
137138
round++;
138139
} while (anotherRoundNeeded);
@@ -480,24 +481,6 @@ const getNodeHoistInfo = (rootNode: HoisterWorkTree, rootNodePathLocators: Set<L
480481
}
481482
}
482483

483-
if (isHoistable) {
484-
// Direct workspace dependencies must be hoisted to any common ancestor workspace of all the
485-
// graph paths that include the dependency, because otherwise running app with
486-
// `--preserve-symlinks` will become broken (without this flag the Node.js will pick dependency
487-
// from the ancestor on the file system and with this flag it will pick ancestor from the graph
488-
// and if these ancestors are different, the behavious of the application will be different).
489-
// Another problem, which is prevented - is a creation of multiple hoisting layouts
490-
// for the same workspace, because different dependencies of the same workspace might be hoisted
491-
// differently, depending on the recepient workspace.
492-
// It is difficult to find all common ancestors, but there is one easy to find common ancestor -
493-
// the root workspace, so, for now, we either hoist direct dependencies into the root workspace, or we keep them
494-
// unhoisted, thus we are safe from various pathological cases with `--preserve-symlinks`
495-
isHoistable = parentNode.dependencyKind !== HoisterDependencyKind.WORKSPACE || parentNode.hoistedFrom.has(node.name) || rootNodePathLocators.size === 1;
496-
if (outputReason && !isHoistable) {
497-
reason = parentNode.reasons.get(node.name)!;
498-
}
499-
}
500-
501484
if (isHoistable) {
502485
isHoistable = !rootNode.peerNames.has(node.name);
503486
if (outputReason && !isHoistable) {

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -534,22 +534,52 @@ describe(`hoist`, () => {
534534
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(2);
535535
});
536536

537-
it(`should avoid hoisting direct workspace dependencies into non-root workspace`, () => {
537+
it(`should hoist direct workspace dependencies into non-root workspace`, () => {
538538
// . -> W1(w) -> W2(w) -> W3(w)-> A@X
539539
// -> A@Y
540540
// -> W3
541541
// -> A@Z
542-
// The A@X must not be hoisted into W2(w)
543-
// otherwise accessing A via . -> W3 with --preserve-symlinks will result in A@Z,
544-
// but accessing it via W3(w) will result in A@Y
542+
// The A@X must be hoisted into W2(w)
543+
// Accessing A via . -> W3 with --preserve-symlinks will result in A@Z,
544+
// but accessing it via W3(w) will result in A@Y, however if we don't do it,
545+
// inner workspaces will have multiple unexpected copies of dependencies
545546
const tree = {
546547
'.': {dependencies: [`W1(w)`, `W3`, `A@Z`], dependencyKind: HoisterDependencyKind.WORKSPACE},
547548
'W1(w)': {dependencies: [`W2(w)`, `A@Y`], dependencyKind: HoisterDependencyKind.WORKSPACE},
548549
'W2(w)': {dependencies: [`W3(w)`], dependencyKind: HoisterDependencyKind.WORKSPACE},
549550
'W3(w)': {dependencies: [`A@X`], dependencyKind: HoisterDependencyKind.WORKSPACE},
550551
};
551552

552-
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(5);
553+
expect(getTreeHeight(hoist(toTree(tree), {check: true}))).toEqual(4);
554+
});
555+
556+
it(`should hoist dependencies to the top from workspaces that have no hoist borders given there is workspace with hoist borders`, () => {
557+
// . -> W1(w)| -> A@X --> B
558+
// -> B@X
559+
// -> W2(w) -> A@Y --> B
560+
// -> B@Y
561+
// should be hoisted to:
562+
// . -> W1(w)| -> A@X -->B
563+
// -> B@X
564+
// -> W2(w)
565+
// -> A@Y --> B
566+
// -> B@Y
567+
568+
const tree = {
569+
'.': {dependencies: [`W1(w)`, `W2(w)`], dependencyKind: HoisterDependencyKind.WORKSPACE},
570+
'W1(w)': {dependencies: [`A@X`, `B@X`], dependencyKind: HoisterDependencyKind.WORKSPACE},
571+
'A@X': {dependencies: [`B@X`], peerNames: [`B`]},
572+
'A@Y': {dependencies: [`B@Y`], peerNames: [`B`]},
573+
'W2(w)': {dependencies: [`A@Y`, `B@Y`], dependencyKind: HoisterDependencyKind.WORKSPACE},
574+
};
575+
576+
const hoistingLimits = new Map([
577+
[`.@`, new Set([`W1(w)`])],
578+
]);
579+
580+
const hoistedTree = hoist(toTree(tree), {check: true, hoistingLimits});
581+
const W2 = Array.from(Array.from(hoistedTree.dependencies).filter(x => x.name === `W2(w)`)[0].dependencies);
582+
expect(W2).toEqual([]);
553583
});
554584

555585
it(`should hoist aliased packages`, () => {

0 commit comments

Comments
 (0)