Skip to content

Commit d9290f8

Browse files
ZheSun88claude
andauthored
fix: install pnpm deps in hoisted mode + declare @babel/types (#24288)
## Summary The Vite dev server intermittently fails (or hangs) on `vite-basics` and other Vite-using IT modules because transitive npm dependencies are not consistently reachable from the project's root `node_modules`. Selenium tests then wait for pages that never render, and the matrix shard times out at the 30-minute job cap. Two distinct symptoms observed: 1. **`Cannot find package '@babel/types'`** — the bundled Vite plugin `react-function-location-plugin.js` does `import * as t from '@babel/types';`, but `@babel/types` was only present transitively via `@babel/core` and pnpm did not always hoist it to the root. 2. **`Failed to resolve import "@lit/reactive-element/..."`, `"cookie"`, `"set-cookie-parser"`, `"@preact/signals-react/runtime"`** — all transitives of declared deps (`lit`, `react-router`, `@preact/signals-react-transform`) — also not consistently hoisted. Both are the same underlying problem: pnpm with `shamefully-hoist=true` does not deterministically expose every transitive at the project root. ## Fix Three commits, layered: ### 1. Declare `@babel/types` directly Adds `"@babel/types": "7.29.0"` (matching `@babel/core`) to the bundled `devDependencies` in `flow-server/.../dependencies/vite/package.json`. Correct on its own merits — the plugin imports the package, so the package should be a real dependency. Updates `NodeUpdaterTest`'s expected-set accordingly. ### 2. Switch the bundled `.npmrc` template to `node-linker=hoisted` Adds `node-linker=hoisted` to `flow-server/src/main/resources/npmrc`, the template `TaskRunNpmInstall` writes to the project. This tells pnpm to install in **flat npm-style mode** — every package (declared or transitive) at the project root, no `.pnpm/` virtual store. Resolutions become deterministic. `shamefully-hoist=true` is left in the template alongside, so manual `pnpm install` invocations from a developer shell still pick up at least the legacy partial-hoist behavior if some user-level config overrides our `node-linker`. ### 3. Pass `--config.node-linker=hoisted` on the pnpm CLI The `.npmrc` change alone did not actually flip pnpm's install layout in CI: `node_modules/.pnpm/...` paths kept appearing in error messages, indicating pnpm was still using its default `isolated` mode. The pre-existing `--shamefully-hoist=true` CLI flag added by `FrontendTools.getPnpmExecutable()` likely took precedence and locked pnpm to the isolated layout (where `shamefully-hoist` makes sense as a partial-hoist heuristic). Replace `--shamefully-hoist=true` with `--config.node-linker=hoisted` on the CLI. CLI > `.npmrc` > defaults in pnpm's config precedence, so the install is now unambiguously hoisted regardless of any project- or user-level `.npmrc` content. `FrontendToolsTest` updated to assert the new flag. ## Trade-offs - **Disk/install time**: hoisted mode uses slightly more disk and is a few seconds slower than the symlink store for fresh installs. Acceptable for the determinism gain. - **Loose isolation**: hoisted mode allows packages to import any other package, declared or not. Flow's frontend code already relies on this (e.g. plugins importing transitives), so this is the current de-facto behavior — just made deterministic. - **Behavioral change for downstream users**: anyone with a custom `.npmrc` that lacks the auto-gen marker keeps their config (per existing `TaskRunNpmInstall.createNpmRcFile()` logic), but the new CLI flag still applies — they'll silently get hoisted mode for Flow-driven installs. Worth flagging in release notes. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a6c4ded commit d9290f8

6 files changed

Lines changed: 19 additions & 3 deletions

File tree

‎flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,13 @@ public List<String> getPnpmExecutable() {
364364
List<String> pnpmCommand = getSuitablePnpm();
365365
assert !pnpmCommand.isEmpty();
366366
pnpmCommand = new ArrayList<>(pnpmCommand);
367-
pnpmCommand.add("--shamefully-hoist=true");
367+
// Force hoisted (flat npm-style) layout. CLI takes precedence over
368+
// .npmrc, so this is unambiguous even if the project lacks the
369+
// generated .npmrc. Replaces the previous --shamefully-hoist=true,
370+
// which only controls the partial-hoist heuristic on top of the
371+
// default isolated layout and did not consistently expose every
372+
// transitive at the project root.
373+
pnpmCommand.add("--config.node-linker=hoisted");
368374
return pnpmCommand;
369375
}
370376

‎flow-build-tools/src/test/java/com/vaadin/flow/server/frontend/FrontendToolsTest.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,9 @@ void knownFaultyNpmVersionThrowsException() {
379379
@Test
380380
void getPnpmExecutable_executableIsAvailable() {
381381
List<String> executable = tools.getPnpmExecutable();
382-
// command line should contain --shamefully-hoist=true option
383-
assertTrue(executable.contains("--shamefully-hoist=true"));
382+
// command line should force hoisted node-linker so transitive
383+
// deps are always installed at the project root
384+
assertTrue(executable.contains("--config.node-linker=hoisted"));
384385
assertTrue(executable.stream().anyMatch(cmd -> cmd.contains("pnpm")));
385386
}
386387

‎flow-build-tools/src/test/java/com/vaadin/flow/server/frontend/NodeUpdaterTest.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ void getDefaultDevDependencies_includesAllDependencies_whenUsingVite() {
169169
expectedDependencies.add("transform-ast");
170170
expectedDependencies.add("strip-css-comments");
171171
expectedDependencies.add("@babel/core");
172+
expectedDependencies.add("@babel/types");
172173
expectedDependencies
173174
.add("@babel/plugin-transform-react-jsx-development");
174175
expectedDependencies.add("@babel/preset-react");

‎flow-build-tools/src/test/java/com/vaadin/flow/server/frontend/TaskRunPnpmInstallTest.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ void runPnpmInstall_npmRcFileNotFound_newNpmRcFileIsGenerated()
223223
assertTrue(npmRcFile.exists());
224224
String content = Files.readString(npmRcFile.toPath());
225225
assertTrue(content.contains("shamefully-hoist"));
226+
assertTrue(content.contains("node-linker=hoisted"));
226227
}
227228

228229
@Test

‎flow-server/src/main/resources/com/vaadin/flow/server/frontend/dependencies/vite/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@rollup/plugin-replace": "6.0.3",
1616
"@rollup/pluginutils": "5.3.0",
1717
"@babel/core": "7.29.0",
18+
"@babel/types": "7.29.0",
1819
"@babel/plugin-transform-react-jsx-development": "7.27.1",
1920
"@babel/preset-react": "7.28.5",
2021
"@rolldown/plugin-babel": "0.2.3",

‎flow-server/src/main/resources/npmrc‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,11 @@
33
#
44
# This file sets the default parameters for manual `pnpm install`.
55
#
6+
# node-linker=hoisted produces a flat node_modules layout (like npm),
7+
# so every transitive dependency lives at the project root. Flow's
8+
# frontend tooling and bundled Vite plugins (e.g. the React function
9+
# location plugin) import transitive deps that pnpm's symlink-based
10+
# layout did not consistently hoist, even with shamefully-hoist=true.
11+
node-linker=hoisted
612
shamefully-hoist=true
713
strict-peer-dependencies=false

0 commit comments

Comments
 (0)