Skip to content

Client build fails on unreachable dynamic-import edge cases (e.g. return; throw;) #21326

@nomi-san

Description

@nomi-san

Describe the bug

A client-only build can fail because Vite's import analysis resolves dynamic imports that are intended to be dead/server-only.

Guards like:

export const preload = async => {
  if (false /*or import.meta.env.SSR*/) {

    // dead code now //

    /* server code */
    const { join } = await import('node:path')
  }
}

— correctly remain dead and are not analyzed by the client build, but patterns that create "syntactically present" dynamic imports after unreachable statements like:

export const preload = async => {
  return;
  return void 0;
  if (true) return;
  throw new Error();

  // dead code now //

  /* server code */
  const { join } = await import('node:path')
}

— are still picked up by the import analyzer and cause the client build to try to resolve server-only modules, resulting in build errors.

vite v7.3.0 building client environment for production...
[plugin vite:resolve] Module "node:path" has been externalized for browser compatibility, imported by "/home/projects/vitejs-vite-a4nfutc9/src/main.js". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
✓ 10 modules transformed.
✗ Build failed in 357ms
error during build:
src/main.js (1:9): "join" is not exported by "__vite-browser-external", imported by "src/main.js".

Expected behavior

Dynamic imports that are unreachable or inside explicit server-only guards (e.g. if (false) { ... } or if (import.meta.env.SSR) { ... }) should not be considered by client-side import analysis and should not be resolved/included by the client build.

Actual behavior

Vite's import analysis still detects and resolves some dynamic imports when they appear syntactically (even if preceded by return;, throw, or other constructs that make them unreachable), causing client builds to fail or incorrectly include server-only modules.

Reproduction

https://stackblitz.com/edit/vitejs-vite-a4nfutc9?file=src%2Fmain.js

Steps to reproduce

Run pnpm install then run pnpm build. In main.js, uncomment preload2 code and run build to see the error.

System Info

System:
    OS: Windows 10 10.0.19045
    Memory: 36.45 GB / 61.83 GB
  Binaries:
    Node: 22.14.0 - C:\Program Files\nodejs\node.EXE
    npm: 10.9.2
    pnpm: 9.15.9
  Browsers:
    Chrome: 143.0.7499.110
    Edge: Chromium (140.0.3485.54)
  npmPackages:
    vite: ^7.3.0 => 7.3.0

Used Package Manager

pnpm

Logs

Click to expand!
❯ pnpm build

> vite-starter@0.0.0 build /home/projects/vitejs-vite-a4nfutc9
> vite build

vite v7.3.0 building client environment for production...
✓ 7 modules transformed.
dist/index.html                 0.45 kB │ gzip: 0.29 kB
dist/assets/index-XlyKSLnJ.css  1.20 kB │ gzip: 0.62 kB
dist/assets/index-CgYMNPpC.js   2.58 kB │ gzip: 1.37 kB
✓ built in 449ms

~/projects/vitejs-vite-a4nfutc9 2s
❯ pnpm build

> vite-starter@0.0.0 build /home/projects/vitejs-vite-a4nfutc9
> vite build

vite v7.3.0 building client environment for production...
[plugin vite:resolve] Module "node:path" has been externalized for browser compatibility, imported by "/home/projects/vitejs-vite-a4nfutc9/src/server-only.js". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
✓ 10 modules transformed.
✗ Build failed in 357ms
error during build:
src/server-only.js (1:9): "join" is not exported by "__vite-browser-external", imported by "src/server-only.js".
file: /home/projects/vitejs-vite-a4nfutc9/src/server-only.js:1:9

1: import { join } from 'node:path';
            ^
2: 
3: join('/a', 'b');

    at getRollupError (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/parseAst.js:568:41)
    at Module.error (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/parseAst.js:564:42)
    at Module.error (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:17047:29)
    at Module.traceVariable (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:17503:29)
    at ModuleScope.findVariable (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:15166:39)
    at Identifier.bind (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:5487:40)
    at CallExpression.bind (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:2854:23)
    at CallExpression.bind (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:12197:15)
    at ExpressionStatement.bind (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:2854:23)
    at Program.bind (file:///home/projects/vitejs-vite-a4nfutc9/node_modules/rollup/dist/es/shared/node-entry.js:2850:28)
 ELIFECYCLE  Command failed with exit code 1.

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions