[GH-ISSUE #1] Virtual module imports break esbuild dependency optimization when vinext is installed from npm #3

Closed
opened 2026-05-06 12:36:20 +02:00 by BreizhHardware · 0 comments

Originally created by @threepointone on GitHub (Feb 24, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/1

Description

When vinext is installed as an npm package (rather than a symlinked local dependency), vite dev fails with:

✘ [ERROR] Could not resolve "virtual:vinext-rsc-entry"

    node_modules/.pnpm/vinext@0.0.1_.../node_modules/vinext/dist/server/app-router-entry.js:15:23:
      15 │ import rscHandler from "virtual:vinext-rsc-entry";
         ╵                        ~~~~~~~~~~~~~~~~~~~~~~~~~~

This happens during esbuild's dependency optimization pass. The optimizer scans vinext/dist/server/app-router-entry.js, encounters the virtual:vinext-rsc-entry import, and fails because virtual modules only exist at Vite plugin resolution time — not during esbuild's pre-bundling scan.

Why this wasn't an issue before

When vinext is a symlinked package ("vinext": "link:../vinext/packages/vinext"), Vite automatically excludes linked packages from dependency optimization. Once installed from npm ("vinext": "^0.0.1"), esbuild starts scanning it and hits the unresolvable virtual imports.

The error occurs in multiple Vite environments (client, rsc, ssr) since app-router-entry.js can be pulled into the dep optimization graph from any of them.

Proposed fix

The vinext plugin should add an esbuild plugin in its config() hook to externalize its own virtual modules during dependency optimization:

// In the vinext plugin config() hook
config() {
  return {
    optimizeDeps: {
      esbuildOptions: {
        plugins: [{
          name: "vinext-externalize-virtual",
          setup(build) {
            build.onResolve({ filter: /^virtual:vinext-/ }, (args) => ({
              path: args.path,
              external: true,
            }));
          },
        }],
      },
    },
  };
}

This is targeted — it only externalizes vinext's own virtual:vinext-* modules during the esbuild scan, without affecting pre-bundling of anything else.

An alternative (less targeted) approach would be to add "vinext" to optimizeDeps.exclude for all environments, but this prevents pre-bundling of all vinext exports even when unnecessary.

Current workaround

Users can add this to their vite.config.ts:

export default defineConfig({
  optimizeDeps: {
    exclude: ["vinext"],
    esbuildOptions: {
      plugins: [{
        name: "externalize-virtual-modules",
        setup(build) {
          build.onResolve({ filter: /^virtual:/ }, (args) => ({
            path: args.path,
            external: true,
          }));
        },
      }],
    },
  },
  environments: {
    rsc: {
      optimizeDeps: {
        exclude: ["vinext"],
      },
    },
    ssr: {
      optimizeDeps: {
        exclude: ["vinext"],
      },
    },
  },
});

This needs to cover all three environments (client via top-level, rsc, ssr) to prevent the error in each.

Originally created by @threepointone on GitHub (Feb 24, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/1 ## Description When vinext is installed as an npm package (rather than a symlinked local dependency), `vite dev` fails with: ``` ✘ [ERROR] Could not resolve "virtual:vinext-rsc-entry" node_modules/.pnpm/vinext@0.0.1_.../node_modules/vinext/dist/server/app-router-entry.js:15:23: 15 │ import rscHandler from "virtual:vinext-rsc-entry"; ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` This happens during esbuild's dependency optimization pass. The optimizer scans `vinext/dist/server/app-router-entry.js`, encounters the `virtual:vinext-rsc-entry` import, and fails because virtual modules only exist at Vite plugin resolution time — not during esbuild's pre-bundling scan. ## Why this wasn't an issue before When vinext is a symlinked package (`"vinext": "link:../vinext/packages/vinext"`), Vite automatically excludes linked packages from dependency optimization. Once installed from npm (`"vinext": "^0.0.1"`), esbuild starts scanning it and hits the unresolvable virtual imports. The error occurs in multiple Vite environments (client, rsc, ssr) since `app-router-entry.js` can be pulled into the dep optimization graph from any of them. ## Proposed fix The vinext plugin should add an esbuild plugin in its `config()` hook to externalize its own virtual modules during dependency optimization: ```ts // In the vinext plugin config() hook config() { return { optimizeDeps: { esbuildOptions: { plugins: [{ name: "vinext-externalize-virtual", setup(build) { build.onResolve({ filter: /^virtual:vinext-/ }, (args) => ({ path: args.path, external: true, })); }, }], }, }, }; } ``` This is targeted — it only externalizes vinext's own `virtual:vinext-*` modules during the esbuild scan, without affecting pre-bundling of anything else. An alternative (less targeted) approach would be to add `"vinext"` to `optimizeDeps.exclude` for all environments, but this prevents pre-bundling of all vinext exports even when unnecessary. ## Current workaround Users can add this to their `vite.config.ts`: ```ts export default defineConfig({ optimizeDeps: { exclude: ["vinext"], esbuildOptions: { plugins: [{ name: "externalize-virtual-modules", setup(build) { build.onResolve({ filter: /^virtual:/ }, (args) => ({ path: args.path, external: true, })); }, }], }, }, environments: { rsc: { optimizeDeps: { exclude: ["vinext"], }, }, ssr: { optimizeDeps: { exclude: ["vinext"], }, }, }, }); ``` This needs to cover all three environments (client via top-level, rsc, ssr) to prevent the error in each.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#3
No description provided.