Skip to content

[Bug]: Bundled CJS module's require("react") left as runtime __require even though react is already inlined elsewhere in the same build #9407

@gazjones00

Description

@gazjones00

Reproduction link or steps

Minimal reproduction (no Nitro, no TanStack, no framework - real world use case outlined in additional comments) - a plain Vite 8 SSR build with rolldown:

package.json

{
  "name": "rolldown-cjs-require-repro",
  "private": true,
  "type": "module",
  "scripts": { "build": "vite build --ssr src/entry.ts --outDir dist" },
  "dependencies": {
    "react": "19.2.6",
    "use-sync-external-store": "1.6.0"
  },
  "devDependencies": {
    "vite": "8.0.13"
  }
}

src/entry.ts

import * as React from "react";
// the CJS shim transitively used by @tanstack/react-router, react-query, etc.
import { useSyncExternalStore } from "use-sync-external-store/shim";

export default function handler() {
  return { React: typeof React, useSyncExternalStore: typeof useSyncExternalStore };
}

vite.config.ts

import { defineConfig } from "vite";

export default defineConfig({
  ssr: { target: "node", noExternal: true },
  build: { ssr: true, target: "node20", minify: false },
});

Steps:

pnpm install
pnpm build
grep -n '__require("react")' dist/entry.mjs
grep -n 'var require_react'     dist/entry.mjs

Both greps return hits. The bundle inlines a full copy of React under var require_react = /* @__PURE__ */ __commonJSMin(...) and leaves __require("react") calls untouched inside the wrapped use-sync-external-store-shim.production.js.

(Happy to push this to a GitHub repo if a maintainer would prefer that — the four files above are the entire repro.)

What is expected?

When rolldown's CJS plugin wraps a CJS module in __commonJSMin(...), the require("id") calls inside that wrapper should be rewritten to reference the bundled module if id is bundled, the same way @rollup/plugin-commonjs rewrites them.

In the repro the build graph already contains React (as the require_react wrapper). The require("react") inside the wrapped use-sync-external-store-shim.production.js should therefore be rewritten to call require_react() — not left as a runtime __require("react").

__require (the Node createRequire-based escape hatch) should only be emitted when:

  1. The require target is external (and listed as external in the config), or
  2. The require expression is dynamic (require(someVar)) and cannot be statically resolved.

Neither applies here.

What is actually happening?

require("react") inside the wrapped CJS module is emitted verbatim as __require("react"), so the deployed code falls back to Node's runtime resolver. When the output is deployed to an environment where react isn't installed in node_modules (e.g. a Nitro/Vercel serverless function whose tracer only follows static imports), the function crashes on first request with:

Error: Cannot find module 'react'
Require stack:
- /var/task/_ssr/<chunk>.mjs
    at Module._resolveFilename (node:internal/modules/cjs/loader:1456:15)
    ...
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/var/task/_ssr/<chunk>.mjs' ]

Excerpt from the produced bundle (real output from our Vite 8 + Nitro 3 + TanStack Start project):

// Inlined React — proves rolldown bundled it:
var require_react_production = /* @__PURE__ */ __commonJSMin(((exports) => { /* full React source */ }));
var require_react = /* @__PURE__ */ __commonJSMin(((exports, module) => {
  module.exports = require_react_production();
}));

// ...elsewhere in the same output graph:

var require_use_sync_external_store_shim_production = /* @__PURE__ */ __commonJSMin(((exports) => {
  var React$1 = __require("react");   // ← should be `require_react()`
  // ...
}));
var require_with_selector_production = /* @__PURE__ */ __commonJSMin(((exports) => {
  var React = __require("react"), shim = require_shim();   // ← same
  // ...
}));

Both require_react and the dangling __require("react") are produced by the same build invocation, so it's not an external/inline disagreement — it's a missed rewrite at the CJS wrapper boundary.

System Info

System:
  OS: macOS 26.2
  CPU: (12) arm64 Apple M3 Pro
Binaries:
  Node: 24.3.0
  npm: 11.4.2
  pnpm: 11.1.2
npmPackages:
  rolldown: 1.0.1 (transitive via vite 8.0.13)
  vite: 8.0.13
  react: 19.2.6
  use-sync-external-store: 1.6.0

Any additional comments?

Hit this in production: a Vite 8 + Nitro 3 + TanStack Start app deployed to Vercel. The function builds successfully but every request 500s with Cannot find module 'react'. The build also inlines React, which made the root cause non-obvious — both the inlined copy and the dangling __require("react") live in the same output graph.

Likely also affects every transitively-CJS React peer dep that uses require("react") at module top level: use-sync-external-store/shim and …/shim/with-selector are the obvious ones, but scheduler, react-side-effect-style packages, and older test/utility libs would behave the same.

Workarounds (none of these should be necessary):

  1. Tell the deployer to copy react/react-dom into the runtime node_modules so the runtime __require("react") resolves. For Nitro: nitro({ traceDeps: ["react", "react-dom"] }).
  2. Mark react/react-dom as external so rolldown emits real ESM import statements (which trackers like Nitro's nft can see) — at the cost of losing the inlined React.
  3. Patch use-sync-external-store to use a static ESM import — not viable, it's a third-party shim.

For comparison, building the same src/entry.ts with Vite 7 (rollup + @rollup/plugin-commonjs) emits a single import React from "react" at the top of the output and no __require("react") calls anywhere — which is the behaviour we'd expect from rolldown.

Happy to help narrow down the failing rewrite path if a maintainer can point me at the relevant part of the CJS pass — it looks like the wrapper-internal require() is being lowered before the bundled-module lookup table is consulted.

Metadata

Metadata

Assignees

No one assigned

    Type

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions