Skip to content

resolve dev-mode Nuxt entry via Vite @fs absolute path - #191

Merged
fago merged 2 commits into
drunomics:1.xfrom
marconett:nuxt-dev
Aug 4, 2026
Merged

resolve dev-mode Nuxt entry via Vite @fs absolute path#191
fago merged 2 commits into
drunomics:1.xfrom
marconett:nuxt-dev

Conversation

@marconett

Copy link
Copy Markdown
Contributor

Problem

In dev mode (nuxt dev), the app-loader fails to load the Nuxt app entry in cross-origin previews (Nuxt frontend embedded in Drupal Canvas):

GET /nuxt-component-preview/app-loader.js   → 200
GET /_nuxt/node_modules/nuxt/dist/app/entry.js   → 404 (see screenshot)
Untitled

The Vite dev server actually serves the entry from:

/_nuxt/@fs/<project>/node_modules/nuxt/dist/app/entry.async.js

nuxt build / nuxt generate are unaffected (they read the real chunk from build:manifest).

Root cause

The if (nuxt.options.dev) branch in src/module.ts synthesizes a build-style entry URL instead of using the form Vite actually serves. Two things are wrong:

  1. Path. It strips appDir to a path relative to rootDir:

    const relativeAppDir = appDir.startsWith(rootDir) ? appDir.slice(rootDir.length + 1) : appDir
    resolvedEntryPath = `/_nuxt/${relativeAppDir}/entry.js` + ...

    The entry lives in node_modules/nuxt/dist/app, which Vite does not serve from a root-relative URL — it serves it via the @fs/<absolute-path> mechanism. For any normal single-project frontend (where appDir is
    under rootDir), this produces /_nuxt/node_modules/nuxt/dist/app/entry.js, which 404s.

    This is also why the playground masks the bug: there appDir (<repo>/node_modules/...) is not under rootDir (<repo>/playground), so the startsWith branch is skipped and the leftover absolute path happens
    to be served by Vite. Real consumer projects don't have that layout.

  2. Filename. Dev uses the async entry. @nuxt/vite-builder resolves useAsyncEntry = experimental.asyncEntry || nuxt.options.dev, so in dev the entry is always entry.async.js, not entry.js.

Fix

Build the dev entry URL from the entry's absolute filesystem path using Vite's canonical @fs/ prefix, and use the async entry filename.

Result: /_nuxt/@fs/<project>/node_modules/nuxt/dist/app/entry.async.js, which matches what the dev server serves.

Scope / safety

  • Change is confined to the if (nuxt.options.dev) branch. The !nuxt.options.dev path (build:manifest, used by both nuxt build and nuxt generate) is untouched.
  • baseURL / buildAssetsDir are now honored for non-default configs.

Verification

  • Playground nuxt dev: the app-loader now emits the @fs URL and it returns 200 (previously the synthesized path; the playground's monorepo layout hid the breakage).
  • Confirmed in a real cross-origin setup (separate-origin Drupal Canvas backend + nuxt dev frontend): preview now renders; entry request returns 200 instead of 404.
  • nuxt build + node .output/server/index.mjs: unchanged, still renders previews.

Acknowledgement

This PR was written with the help of AI, tho I manually verified and tested it within my nuxt (v4.4.6) project.

Copilot AI review requested due to automatic review settings June 9, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates how the module computes the Nuxt app entry URL in dev mode so it reliably resolves under Vite (including when the entry lives under node_modules) and uses the correct async entry file.

Changes:

  • Switches dev entry URL generation to Vite’s @fs/<absolute-path> mechanism.
  • Accounts for baseURL and buildAssetsDir when constructing the entry URL.
  • Selects entry.async.js in dev (matching Nuxt Vite behavior), with buildId cache-busting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/module.ts Outdated
Comment thread src/module.ts Outdated
Comment thread src/module.ts
Comment thread src/module.ts Outdated
@marconett

Copy link
Copy Markdown
Contributor Author

Hi, is it possible to get this merged?

We're currently evaluating the whole Drupal Canvas -> NuxtJS (via Lupus) setup for a long term project and are a bit unsure in regards to long term maintainability and support.

@fago

fago commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@marconett hey! I'm sry, I completely missed your PR, it got lost between all the auto-created update PRs. I need to configure this better so this won't happen again. :(

Are you still facing the problem? Which custom_elements and canvas_extjs modules are you using?

@marconett

marconett commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @fago I just updated my setup to the most recent supported versions:

  • "drupal/canvas": "1.8.0"
  • "drupal/canvas_extjs": "1.2.1"
  • "drupal/custom_elements": "3.4.1"
  • "drupal/lupus_decoupled": "1.5.1"
  • "nuxtjs-drupal-ce": "2.7.0"
  • "nuxt-component-preview": "1.0.1"

The problem persists. I updated my patch to support windows (as per Copilot comments) and confirmed that my patch still fixes the issue.

@drubot

drubot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Claude Code: reviewed and reproduced this end-to-end (on behalf of @fago, ticket CSR-172).

Verdict: the fix is correct and the bug is real. I could reproduce the 404 on 1.x and confirm the patch resolves it, including in the non-default baseURL / buildAssetsDir and pnpm layouts. Two small non-blocking cleanups below; the merge decision is @fago's.

Reproduction

The trigger is narrower than "appDir under rootDir" — it is Nuxt 4's default app/ srcDir layout. Vite's dev-server root is srcDir, so the synthesized root-relative URL /_nuxt/node_modules/nuxt/dist/app/entry.js is resolved against <root>/app/, where node_modules does not exist → 404.

Matrix (fresh consumer project, nuxt-component-preview linked from source, nuxi dev):

Layout 1.x PR
nuxt 4.5.1, app.vue at root (srcDir = rootDir) 200 (bug masked) 200
nuxt 4.4.6, app.vue at root 200 (bug masked) 200
nuxt 4.4.6, app/app.vue (Nuxt 4 default srcDir) 404 200
nuxt 4.4.6, app/ srcDir + baseURL: '/app/' + buildAssetsDir: '/assets/' 404 200
nuxt 4.4.6, app/ srcDir, pnpm (node_modules/.pnpm/...) 404 200

Browser check (headless Chromium loading a page with <script src="/nuxt-component-preview/app-loader.js">):

  • 1.x: GET /_nuxt/node_modules/nuxt/dist/app/entry.js404, #__nuxt stays empty, app never mounts.
  • PR: GET /_nuxt/@fs/<abs>/node_modules/nuxt/dist/app/entry.async.js200, app mounts, no console errors.

With baseURL: '/app/' + buildAssetsDir: '/assets/' the PR emits /app/assets/@fs/<abs>/…/entry.async.js and it resolves — the prefix handling is right (app-loader.js composes cdnURL + entryPath, and cdnURL is an origin, so no double prefix).

Regression suite

Merged the PR into 1.x locally (clean merge, only src/module.ts changes) and ran everything:

  • npm test — 128 passed / 7 files ✅
  • npm run test:e2e — 24 passed / 5 files ✅ (covers dev and the nuxt build + prod-server preview path)
  • npm run lint — 0 errors ✅ (5 pre-existing warnings in test/cdn-fetch-paths.test.ts, unrelated)

The !nuxt.options.dev / build:manifest branch is untouched, and the prod e2e confirms build behaviour is unchanged.

Nitpicks (non-blocking)

  1. The playground cannot guard this. Its appDir sits at the repo root (outside rootDir = playground/), so the old code leaked a raw absolute path — which Vite happens to serve. I also tried moving playground/app.vueplayground/app/app.vue: still 200 on 1.x, so no playground layout tweak reproduces it either. A realistic fixture would need its own node_modules. Cheaper guard: a unit assertion on the emitted URL shape — that the dev entry path is ${baseURL}${buildAssetsDir}@fs/…/entry.async.js — which pins the contract without a new fixture.

  2. Comment block narrates history. The (A prior implementation stripped appDir down to a node_modules/... path relative to rootDir; that 404s …) sentence describes the change rather than the current constraint. drunomics convention is that comments describe the status quo and the git log holds the history — worth trimming to the "why" (Vite serves node_modules via @fs; dev always uses the async entry).

  3. encodeURI leaves # and ? unescaped. A project path containing either would produce a broken URL. Exotic enough to ignore, but encodeURI(...).replace(/[#?]/g, encodeURIComponent) would close it.

  4. Windows is reasoned-about, not tested here (Linux only): C:\projC:/proj/C:/proj/_nuxt/@fs/C:/proj/…, which matches Vite's /@fs/C:/… form. Looks right; someone on Windows confirming would be nice.

  5. Unrelated pre-existing observation: nuxt.options.appConfig?.nuxt?.buildId is undefined in the ready hook on both branches, so the ?v= dev cache-buster never actually applies. Not introduced by this PR.

  6. The branch is based on 1.0.0-rc.4 (June) but merges cleanly into 1.x — no rebase needed, just noting it.

Thanks @marconett — the analysis in the PR description was accurate and the patch is minimal and well-scoped.

@fago

fago commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

I ran some manual testing to make sure this works well with vite7/nuxt4.4 and nuxt4.5/vite8 setup. Seems the culprit is the is repo-layout anyway!

Thank you, a good fix! Merged!

@fago
fago merged commit dc17a6a into drunomics:1.x Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants