Repository navigation
fix(vite): resolve nested node_modules requests for install-linked dependencies - #11559
Merged
NathanWalker merged 2 commits intoOct 21, 2026
Conversation
…pendencies Dependencies installed as symlinks to source checkouts (link:/file:/portal: specs, or registry specs replaced by install-time hooks like a pnpm readPackage link) keep their own node_modules outside the project root and outside any monorepo workspace root. A device request for a private plugin inside such a checkout 404'd on three counts: - resolveCandidateFilePath only probed the project root and the workspace root, never linked dependency roots; - filterExistingNodeModulesTransformCandidates returned the candidate URL unchanged whenever no distinct workspace root existed or the found file sat inside projectRoot — even when the URL did not address that file; - server.fs.allow only widened to the monorepo root, so an anchored /@fs/<external checkout> path was refused by isFileLoadingAllowed. getLinkedDependencyRoots collects link:/file:/portal: targets and node_modules symlinks whose realpath leaves any node_modules dir (which excludes registry installs and external stores). The resolver probes each linked root's node_modules plus its workspace's dirs with the same /node_modules/ tail scan used for monorepo fallbacks; candidates are kept unchanged only when the URL addresses the file found; and fs.allow gains each linked root's workspace root.
|
View your CI Pipeline Execution ↗ for commit 3efe16d
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
… install Candidates are resolved one extension/index variant at a time, so a variant present only in a linked checkout's copy of a package the app also installs was served ahead of the app's own file, mixing two copies of the same package.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
What is the current behavior?
Some apps install a dependency as a symlink to a source checkout outside the app. This happens with
link:,portal:, orfile:<dir>, or when a pnpmreadPackagehook links a checkout whilepackage.jsonkeeps a version like"auth": "1.0.0".If that checkout has its own
node_modules(for example, a native plugin that onlyauthdepends on), the device can't load from it:Three things cause this, and each one alone is enough to produce the 404:
resolveCandidateFilePathonly looks under the project root and the monorepo workspace root. It never looks where the linked dependency actually lives.filterExistingNodeModulesTransformCandidatescan find the file somewhere else but still return the original/node_modules/...URL, which points at a file that doesn't exist. This happens when there is no separate workspace root, or when the file is inside the project. For example, withauth: link:./framework/packages/auth, the file is found atframework/packages/auth/node_modules/@scope/sign-in/common.js, but Vite is asked fornode_modules/@scope/sign-in/common.js.server.fs.allowonly adds the monorepo root. Vite refuses/@fs/paths inside an external checkout even when the URL is right.What is the new behavior?
New
getLinkedDependencyRoots(projectRoot)inhelpers/project.tslists the directories of declared dependencies that are source checkouts:link:,portal:andfile:<dir>targets;node_modules/<name>symlinks whose real path is outside anynode_modules. This excludes registry installs and package stores.The result is cached per project root.
resolveCandidateFilePathkeeps its current lookup order. When the project and workspace lookups miss, it also checks each linked checkout'snode_modules, then the packages of that checkout's workspace. It reuses the existinglistWorkspaceNodeModulesDirsscan.filterExistingNodeModulesTransformCandidatesreturns a candidate unchanged only when it points at the file that was found. Otherwise it returns/@fs<found path>.resolveFsAnchoredSpecreturns the path it found instead of the path it guessed.configuration/base.tsgetsserver.fs.allowfrom a newgetDevServerFsAllow(projectRoot). It adds each linked checkout's workspace root (or the checkout itself if it has none). When nothing needs adding it returns[], so the config is unchanged for apps without linked checkouts.Reproduction
The app depends on
"auth": "1.0.0".app/node_modules/authis a symlink tocheckout/packages/auth, and that package has@scope/sign-inin its ownnode_modules. The test runs a real Vite 8.3.4 server and serves/ns/mthroughcreateNsMRequestContextandresolveNsMTransformedModule.@nativescript/vite@8.0.19/@fs/…/checkout/packages/auth/node_modules/@scope/sign-in/index.android.jsfs.allowchangeTests
The new specs are in
helpers/project.spec.ts,websocket-module-specifiers.spec.tsandwebsocket-ns-m-request.spec.ts. The resolution and anchoring cases fail onmain.npx vitest runinpackages/vite: 1853 passed, 1 skipped.Notes
fs.allowbecomes[projectRoot, ...linkedRoots]instead of Vite's defaultsearchForWorkspaceRoot(projectRoot). These differ only when Vite detects a workspace marker thatfindMonorepoWorkspaceRootignores, such as apnpm-workspace.yamlthat only holds settings. The existing monorepo branch already behaves this way.