Skip to content

fix(build): experimental flag to rewrite leaked runtime require of bundled deps (#4171)#4365

Draft
pi0x wants to merge 1 commit into
mainfrom
fix/ext-react
Draft

fix(build): experimental flag to rewrite leaked runtime require of bundled deps (#4171)#4365
pi0x wants to merge 1 commit into
mainfrom
fix/ext-react

Conversation

@pi0x

@pi0x pi0x commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

[ai assited] resolves #4171 via a workaround until upstream issue is fixed

Try:

  • Use "nitro": "https://pkg.pr.new/nitro@4365" in package.json
  • Inside nitro.config.ts add experimental: { cjsRequireRewrite: "react" }

Vite's SSR build lowers a require("react") inside vendored CommonJS (e.g. use-sync-external-store's shim, as shipped inside base-ui / recharts / reactflow) to a runtime __require("react") (createRequire), because React is an SSR-external. Once Nitro bundles React for a self-contained .output/, that require is left dangling:

  • Cannot find module 'react' when node_modules isn't shipped (Docker runner, serverless), or
  • Invalid hook call / null dispatcher when it is__require loads a second React instance.

Root cause is the Vite SSR lowering (pure Rolldown links the same nested require correctly); this is a downstream workaround.

This PR adds an opt-in experimental.cjsRequireRewrite flag — a generateBundle pass that rewrites leaked __require("x") to the copy of x already bundled in the output, so a single instance is used and the output stays self-contained.

  • "react" — targets the React family (react, react-dom, react/jsx-runtime, …).
  • true — covers any bundled dependency that leaked a runtime require.
  • unset (default) — disabled, no behavior change.

Unresolved leaks emit a warning instead of shipping silently.

Vite's SSR build lowers a `require("react")` inside vendored CommonJS to a
runtime `__require("react")` (createRequire) because React is an SSR-external.
Once Nitro bundles React for a self-contained `.output/`, that require is left
dangling: it fails with `Cannot find module 'react'` when node_modules is not
shipped, or loads a duplicate React instance ("Invalid hook call") when it is.

Add an opt-in `experimental.cjsRequireRewrite` flag that rewrites such leaked
requires to the copy already bundled in the output. `"react"` targets the React
family; `true` covers any bundled dependency. Unresolved leaks warn instead of
shipping silently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pi0x pi0x requested a review from pi0 as a code owner June 21, 2026 09:12
@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Jun 21, 2026 9:14am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

A new nitro:cjs-require Rollup/Nitro build plugin is added to rewrite leaked runtime __require("specifier") calls in bundled SSR output chunks. The feature is gated by a new experimental.cjsRequireRewrite option (boolean | "react") in NitroOptions, wired conditionally into baseBuildPlugins, and covered by a unit test suite.

Changes

CJS require rewrite plugin

Layer / File(s) Summary
Config option and plugin registration
src/types/config.ts, src/build/plugins.ts
Adds experimental.cjsRequireRewrite?: boolean | "react" to NitroOptions, imports the new plugin, and conditionally registers cjsRequire(nitro, mode) in baseBuildPlugins with mode set to "all" for true or "react" otherwise.
Plugin core: chunk scanning, initializer resolution, and rewriting
src/build/plugins/cjs-require.ts
Defines CjsRequireMode, React-mode initializer constants, and the full generateBundle hook: scans all output chunks for __require("…") call sites and CJS initializer definitions, resolves an initializer per leaked specifier, injects missing export { initVar as alias } into provider chunks, rewrites consumer chunks to call the initializer (prepending cross-chunk imports when needed), and warns on unresolvable bundled specifiers. Includes helpers for initializer name derivation, package extraction, bundled-package detection, export-alias parsing, relative import path, shebang-safe prepend, and regex escaping.
Unit tests
test/unit/cjs-require.test.ts
Tests rewrite and warning behavior across default, "react", and "all" modes, including export injection, external require passthrough, missing-initializer warnings, and no-op on clean bundles.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully addresses issue #4171 by implementing the cjsRequireRewrite mechanism to rewrite leaked __require() calls for bundled dependencies, resolving the 'Cannot find module react' error.
Out of Scope Changes check ✅ Passed All changes are directly within scope: new plugin implementation, configuration type updates, and comprehensive unit tests for the cjsRequireRewrite feature.
Title check ✅ Passed The title follows conventional commits format with 'fix' prefix, descriptive scope '(build)', and clear summary of the change addressing an experimental flag for rewriting leaked runtime require calls.
Description check ✅ Passed The PR description comprehensively explains the problem (leaked __require calls), the root cause (Vite SSR lowering), and the solution (cjsRequireRewrite flag with two modes).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ext-react

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/unit/cjs-require.test.ts (1)

27-144: ⚡ Quick win

Add coverage for single-quoted leaked requires

Please add a case for __require('react') (and/or another specifier) to lock in quote-agnostic rewrite behavior and avoid regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/cjs-require.test.ts` around lines 27 - 144, Add a new test case in
the describe block after the first it("rewrites a leaked __require() to the
bundled initializer") test to verify single-quoted leaked requires are handled
correctly. Create a test similar to the existing one but use single quotes in
the __require call (e.g., __require('react') instead of __require("react")) and
verify that the rewrite logic still produces the correct import statement and
variable assignment regardless of quote style. This ensures the rewrite behavior
is quote-agnostic and prevents regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/build/plugins/cjs-require.ts`:
- Around line 28-29: The LEAK_RE regex pattern at the current location only
matches double-quoted require calls like __require("...") but fails to match
single-quoted variants like __require('...'). Update the LEAK_RE regex pattern
to match both single and double quotes around the require path argument.
Similarly, update the DEF_RE regex pattern (also mentioned in the review at line
85) to match both quote styles for the require definition pattern. Modify both
regex patterns to use a character class or alternation to capture both quote
types, ensuring that leak detection and rewriting work correctly regardless of
which quote style is used in the generated output.

---

Nitpick comments:
In `@test/unit/cjs-require.test.ts`:
- Around line 27-144: Add a new test case in the describe block after the first
it("rewrites a leaked __require() to the bundled initializer") test to verify
single-quoted leaked requires are handled correctly. Create a test similar to
the existing one but use single quotes in the __require call (e.g.,
__require('react') instead of __require("react")) and verify that the rewrite
logic still produces the correct import statement and variable assignment
regardless of quote style. This ensures the rewrite behavior is quote-agnostic
and prevents regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e93b040a-265d-4630-91e6-616d1a2cd1fc

📥 Commits

Reviewing files that changed from the base of the PR and between f366b25 and 27169ad.

📒 Files selected for processing (4)
  • src/build/plugins.ts
  • src/build/plugins/cjs-require.ts
  • src/types/config.ts
  • test/unit/cjs-require.test.ts

Comment on lines +28 to +29
const LEAK_RE = /\b__require\(\s*"([^"]+)"\s*\)/g;
const DEF_RE = /\bvar (require_[A-Za-z0-9_$]+)\s*=[^\n]*__commonJS/g;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle single-quoted __require() calls too

LEAK_RE and the replacement regex only match __require("..."). If output uses __require('...'), the leak won’t be detected or rewritten, so runtime failures can persist in self-contained builds.

Suggested patch
-const LEAK_RE = /\b__require\(\s*"([^"]+)"\s*\)/g;
+const LEAK_RE = /\b__require\(\s*["']([^"'\\]+)["']\s*\)/g;
@@
-        const requireRe = new RegExp(`__require\\(\\s*"${escapeRe(spec)}"\\s*\\)`, "g");
+        const requireRe = new RegExp(
+          `__require\\(\\s*["']${escapeRe(spec)}["']\\s*\\)`,
+          "g"
+        );

Also applies to: 85-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/build/plugins/cjs-require.ts` around lines 28 - 29, The LEAK_RE regex
pattern at the current location only matches double-quoted require calls like
__require("...") but fails to match single-quoted variants like
__require('...'). Update the LEAK_RE regex pattern to match both single and
double quotes around the require path argument. Similarly, update the DEF_RE
regex pattern (also mentioned in the review at line 85) to match both quote
styles for the require definition pattern. Modify both regex patterns to use a
character class or alternation to capture both quote types, ensuring that leak
detection and rewriting work correctly regardless of which quote style is used
in the generated output.

@pkg-pr-new

pkg-pr-new Bot commented Jun 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4365

commit: 27169ad

@pi0 pi0 changed the title fix(build): rewrite leaked runtime require of bundled deps (#4171) fix(build): experimental flag to rewrite leaked runtime require of bundled deps (#4171) Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot find module 'react' in Docker runner: use-sync-external-store/shim leaves __require('react') unresolved in SSR chunk

2 participants