← 输入框|github.com|刷新
Skip to content

fix(vite): resolve nested node_modules requests for install-linked dependencies - #11559

Merged
NathanWalker merged 2 commits into
NativeScript:mainfrom
aleclarson:fix/hmr-linked-dep-node-modules-fallback
Oct 21, 2026
Merged

NathanWalker merged 2 commits into
NativeScript:mainfrom
aleclarson:fix/hmr-linked-dep-node-modules-fallback

Conversation

@aleclarson

@aleclarson aleclarson commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

PR Checklist

  • The PR title follows our guidelines.
  • There is an issue for the bug/feature this PR is for.
  • You have signed the CLA.
  • All existing tests are passing.
  • Tests for the changes are included.

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:, or file:<dir>, or when a pnpm readPackage hook links a checkout while package.json keeps a version like "auth": "1.0.0".

If that checkout has its own node_modules (for example, a native plugin that only auth depends on), the device can't load from it:

GET /ns/m/node_modules/@scope/sign-in/index  → 404

Three things cause this, and each one alone is enough to produce the 404:

  1. resolveCandidateFilePath only looks under the project root and the monorepo workspace root. It never looks where the linked dependency actually lives.
  2. filterExistingNodeModulesTransformCandidates can 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, with auth: link:./framework/packages/auth, the file is found at framework/packages/auth/node_modules/@scope/sign-in/common.js, but Vite is asked for node_modules/@scope/sign-in/common.js.
  3. server.fs.allow only 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) in helpers/project.ts lists the directories of declared dependencies that are source checkouts:

    • link:, portal: and file:<dir> targets;
    • node_modules/<name> symlinks whose real path is outside any node_modules. This excludes registry installs and package stores.

    The result is cached per project root.

  • resolveCandidateFilePath keeps its current lookup order. When the project and workspace lookups miss, it also checks each linked checkout's node_modules, then the packages of that checkout's workspace. It reuses the existing listWorkspaceNodeModulesDirs scan.

  • filterExistingNodeModulesTransformCandidates returns a candidate unchanged only when it points at the file that was found. Otherwise it returns /@fs<found path>.

  • resolveFsAnchoredSpec returns the path it found instead of the path it guessed.

  • configuration/base.ts gets server.fs.allow from a new getDevServerFsAllow(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/auth is a symlink to checkout/packages/auth, and that package has @scope/sign-in in its own node_modules. The test runs a real Vite 8.3.4 server and serves /ns/m through createNsMRequestContext and resolveNsMTransformedModule.

Build Result
@nativescript/vite@8.0.19 404, no candidates
this PR 200, served from /@fs/…/checkout/packages/auth/node_modules/@scope/sign-in/index.android.js
this PR without the fs.allow change 404, Vite refuses the path

Tests

The new specs are in helpers/project.spec.ts, websocket-module-specifiers.spec.ts and websocket-ns-m-request.spec.ts. The resolution and anchoring cases fail on main.

npx vitest run in packages/vite: 1853 passed, 1 skipped.

Notes

  • If several linked checkouts contain the same package, the first match wins. The URL doesn't say which package imported it. The existing workspace scan has the same limitation.
  • If an app has linked checkouts but no monorepo root, fs.allow becomes [projectRoot, ...linkedRoots] instead of Vite's default searchForWorkspaceRoot(projectRoot). These differ only when Vite detects a workspace marker that findMonorepoWorkspaceRoot ignores, such as a pnpm-workspace.yaml that only holds settings. The existing monorepo branch already behaves this way.
  • Not tested on a device.

…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.
@nx-cloud

nx-cloud Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 3efe16d

Command Status Duration Result
nx test apps-automated -c=android ✅ Succeeded 3m 4s View ↗
nx run-many --target=test --configuration=ci --... ✅ Succeeded 5s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-21 18:37:47 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nativescript/core@11559
npm i https://pkg.pr.new/@nativescript/vite@11559
npm i https://pkg.pr.new/@nativescript/webpack@11559

commit: 3efe16d

… 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.
@NathanWalker
NathanWalker merged commit 5df9d63 into NativeScript:main Oct 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants