Skip to content

vinext should give an actionable error (or auto-exclude) when a CJS dep fails Rolldown's dep-pre-bundler #1220

@eashish93

Description

@eashish93

TL;DR

When a Node-only CJS package (e.g. wpapi, superagent) lives anywhere in the import graph, vinext dev crashes at startup with a raw Rolldown stack trace and exits the Node process — no hint that the fix is to add the package to optimizeDeps.exclude. With nodejs_compat enabled, these packages would work at runtime in workerd; the only failure is the build-time dep optimizer.

It took us several hours to identify which package was the trigger and find the workaround. A short, named diagnostic from vinext (or an auto-exclude heuristic) would make this trivial.

Versions

  • vinext: 0.0.50
  • vite: 8.0.13
  • rolldown: bundled with Vite 8 (node_modules/rolldown/...)
  • @cloudflare/vite-plugin: 1.37.0
  • compatibility_date: 2026-04-21
  • compatibility_flags: ["nodejs_compat"]

Symptom

With optimizeDeps.exclude left empty (the natural starting state), running bun run dev:

file:///.../node_modules/vinext/dist/server/socket-error-backstop.js:114
		throw err;
		^

Error: Error during dependency optimization:

Build failed with 1 error:

[UNRESOLVED_IMPORT] Could not resolve '../..' in node_modules/superagent/lib/node/agent.js

node_modules/superagent/lib/node/agent.js:10:25
 10 │ const request = require('../..');
    │                         ───┬───
    │                            ╰───── Module not found.
    │
    │ Help: 'node_modules/superagent/lib/node/agent.js' is imported by the following path:
    │         - node_modules/superagent/lib/node/agent.js
    │         - node_modules/superagent/lib/node/index.js
    │         - node_modules/wpapi/lib/http-transport.js
    │         - node_modules/wpapi/wpapi.js
    at aggregateBindingErrorsIntoJsError (.../rolldown/dist/shared/error-CkdMJ9ps.mjs:48:18)
    at unwrapBindingResult (.../rolldown/dist/shared/error-CkdMJ9ps.mjs:18:128)
    at #build (.../rolldown/dist/shared/rolldown-build-BVD3dIdE.mjs:3275:34)
    at async Object.build (.../vite/dist/node/chunks/node.js:31729:18)

Node.js v25.2.1

Dev server exits with non-zero status. No HTTP server starts.

Reproduce

Pull any package that uses CJS require('../..') (self-reference to its own package root) into your app graph. The simplest:

// lib/use-wpapi.ts
import WPAPI from 'wpapi';
export const wp = new WPAPI({ endpoint: 'https://example.com/wp-json' });

Then vinext dev — boom.

(superagent itself is enough; wpapi just transitively pulls it in.)

Root cause

Rolldown's dep optimizer doesn't resolve require('../..') inside superagent/lib/node/agent.js. This is a Rolldown limitation, not strictly a vinext bug — but vinext is the framework that surfaces this to users.

Why this matters for a Workers-targeted framework

With compatibility_flags: ["nodejs_compat"] and a recent compat date, node:http, node:https, node:fs, node:net are all 🟢 supported in workerd (docs). So packages like wpapi/superagent would run fine at the edge. The only reason they break is the dev-time dep optimizer — a tooling artefact, not a runtime constraint.

The workaround (optimizeDeps.exclude: ['wpapi', 'superagent']) is one line, but is undiscoverable without:

  1. Knowing what "dep optimizer" means in Vite-speak
  2. Recognising that "fix this Rolldown unresolved-import" → "exclude from optimization"
  3. Reading the import chain in the error to identify which package to exclude

Asks (in order of helpfulness)

1. Friendly diagnostic wrapper. When the dep optimizer fails with UNRESOLVED_IMPORT, vinext catches the error and prints:

[vinext] Dependency optimization failed for "superagent" (pulled in via wpapi).

This is usually a Node-only CJS package that Rolldown's dep-bundler can't pre-build.
At runtime, workerd with nodejs_compat will run it fine.

Fix: add it to optimizeDeps.exclude in vite.config.ts:

  optimizeDeps: {
    exclude: ['wpapi', 'superagent'],
  }

(Both the leaf package and the package that imports it should be listed.)

Even just printing the package names from the import chain + the one-line fix would save users hours.

2. Auto-exclude on Rolldown UNRESOLVED_IMPORT. On the first dep-optimization run, if a package fails with this specific error code, vinext could:

  • Auto-exclude it for the current dev session
  • Print a message: "Auto-excluding superagent from optimizeDeps — add this to your vite.config.ts to make it permanent"

3. Bundled known-broken list. Vinext could ship a tiny known-broken-CJS list (wpapi, superagent, anything else commonly hit) and add it to optimizeDeps.exclude by default. Users can opt out.

Even just (1) alone would make the difference between a 10-second fix and a multi-hour debugging session.

Bonus context

We also hit a separate, much more cryptic dep-runtime crash from the same family — Cannot read properties of undefined (reading '__cjs_module_runner_transform') inside @vitejs/plugin-rsc's helper. That one's an upstream plugin-rsc issue (worth its own diagnostic), but the user-facing fix in both cases is the same: add the package to optimizeDeps.exclude. A single well-worded vinext error message could cover both.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions