[PR #467] [CLOSED] fix: React 19 dev-mode compatibility with Vite module runner #591

Closed
opened 2026-05-06 13:08:57 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/467
Author: @benfavre
Created: 3/11/2026
Status: Closed

Base: mainHead: fix/react19-dev-mode-compat


📝 Commits (1)

  • ef149f7 fix: React 19 dev-mode compatibility with Vite module runner

📊 Changes

3 files changed (+198 additions, -0 deletions)

View changed files

📝 packages/vinext/src/index.ts (+6 -0)
packages/vinext/src/plugins/patch-react-server-dom.ts (+125 -0)
packages/vinext/src/plugins/strip-react-type-imports.ts (+67 -0)

📄 Description

Problem

React 19's react-server-dom-webpack development builds include aggressive error/stack reconstruction logic that crashes in Vite's module runner. Every vinext user running React 19 in dev mode hits these crashes — they block the dev server from serving any page.

Crash 1: debugStack null vs undefined

TypeError: Cannot read properties of undefined (reading 'stack')
    at parseStackTrace

React's code uses null !== task.debugStack (strict equality), but in Vite's module runner task.debugStack is undefined (not null). undefined !== null is true, so the guard passes and parseStackTrace(undefined, 1) crashes on undefined.stack.

Crash 2: parseStackTrace with undefined error

TypeError: Cannot read properties of undefined (reading 'stack')
    at parseStackTrace

Called with undefined error objects in certain code paths.

Crash 3: console.createTask in module runner

TypeError: console.createTask is not a function

V8's console.createTask() devtools API behaves differently in Vite's module runner context.

Crash 4: buildFakeCallStack with undefined stack

Called with undefined stack argument, crashes during stack frame reconstruction.

Crash 5: resolveErrorDev stack reconstruction

Full stack reconstruction using buildFakeCallStack + console.createTask crashes without proper element ownership metadata (vinext's __wrapper, @vitejs/plugin-rsc's Resources lack _debugStack/_debugTask).

Crash 6: "without development properties"

Error: An element was created without development properties

Framework internals create elements without dev metadata. Production builds ignore this; dev builds throw.

Crash 7: Named export 'ReactNode' not found

SyntaxError: Named export 'ReactNode' not found.
The requested module 'react' is a CommonJS module

esbuild (Vite's default TS transform) cannot determine that import { ReactNode } from "react" is type-only without full TypeScript type information. It preserves the import, and Vite's module runner fails because the pre-bundled CJS react module only has runtime exports.

Solution

Two new Vite plugins, registered in vinext's plugin array:

Plugin 1: vinext:patch-react-server-dom

Applies 6 targeted string-replacement patches to react-server-dom-webpack dev builds at transform time:

# Patch Technique
1 null !== task.debugStacknull != task.debugStack Loose inequality catches both null and undefined
2 parseStackTrace(error, skipFrames) Early return [] when error == null
3 supportsCreateTask = !!console.createTask Set to false
4 buildFakeCallStack(response, stack, ...) Early return innerCall when !stack
5 resolveErrorDev(response, errorInfo) Replace with simple Error constructor preserving metadata
6 throw Error("...without development properties...") Replace with void 0

Targets both standalone react-server-dom-webpack and the vendor copy in @vitejs/plugin-rsc/dist/vendor/react-server-dom/.

Plugin 2: vinext:strip-react-type-imports

Maintains the complete set of React 19 runtime exports. For import { ... } from "react" statements, strips any specifier not in the runtime set:

// Before (esbuild preserves this):
import { useState, ReactNode, useEffect } from "react"

// After (plugin strips ReactNode):
import { useState, useEffect } from "react"

Handles edge cases:

  • import type { ... } → skipped entirely
  • import { type ReactNode, useState } → inline type annotation respected
  • import { ReactNode as RN } → aliases resolved correctly
  • import { ReactNode } → replaced with comment placeholder

Changes

File Change
packages/vinext/src/plugins/patch-react-server-dom.ts New plugin (125 lines)
packages/vinext/src/plugins/strip-react-type-imports.ts New plugin (67 lines)
packages/vinext/src/index.ts Import + register both plugins

Reproduction

Any app using React 19 with vinext in development mode:

npx create-next-app@latest my-app
cd my-app
npm install vinext
# Add vinext to vite.config.ts
npm run dev
# → Multiple crashes from react-server-dom-webpack dev builds

Risk

Low. Both plugins:

  • Only match development builds (.development. in filename)
  • Use enforce: "pre" so they run before other transforms
  • Only apply when the target strings are found (no-op on production builds)
  • Are idempotent (safe to re-run)

The strip-react-type-imports plugin is conservative — it only strips names that are not in the React 19 runtime export list. If React adds new runtime exports in the future, the allowlist needs to be updated.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/467 **Author:** [@benfavre](https://github.com/benfavre) **Created:** 3/11/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/react19-dev-mode-compat` --- ### 📝 Commits (1) - [`ef149f7`](https://github.com/cloudflare/vinext/commit/ef149f75ba8bfda714731d95339f2ae8ba8da145) fix: React 19 dev-mode compatibility with Vite module runner ### 📊 Changes **3 files changed** (+198 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/index.ts` (+6 -0) ➕ `packages/vinext/src/plugins/patch-react-server-dom.ts` (+125 -0) ➕ `packages/vinext/src/plugins/strip-react-type-imports.ts` (+67 -0) </details> ### 📄 Description ## Problem React 19's `react-server-dom-webpack` development builds include aggressive error/stack reconstruction logic that crashes in Vite's module runner. Every `vinext` user running React 19 in dev mode hits these crashes — they block the dev server from serving any page. ### Crash 1: `debugStack` null vs undefined ``` TypeError: Cannot read properties of undefined (reading 'stack') at parseStackTrace ``` React's code uses `null !== task.debugStack` (strict equality), but in Vite's module runner `task.debugStack` is `undefined` (not `null`). `undefined !== null` is `true`, so the guard passes and `parseStackTrace(undefined, 1)` crashes on `undefined.stack`. ### Crash 2: `parseStackTrace` with undefined error ``` TypeError: Cannot read properties of undefined (reading 'stack') at parseStackTrace ``` Called with `undefined` error objects in certain code paths. ### Crash 3: `console.createTask` in module runner ``` TypeError: console.createTask is not a function ``` V8's `console.createTask()` devtools API behaves differently in Vite's module runner context. ### Crash 4: `buildFakeCallStack` with undefined stack Called with `undefined` stack argument, crashes during stack frame reconstruction. ### Crash 5: `resolveErrorDev` stack reconstruction Full stack reconstruction using `buildFakeCallStack` + `console.createTask` crashes without proper element ownership metadata (vinext's `__wrapper`, `@vitejs/plugin-rsc`'s Resources lack `_debugStack`/`_debugTask`). ### Crash 6: "without development properties" ``` Error: An element was created without development properties ``` Framework internals create elements without dev metadata. Production builds ignore this; dev builds throw. ### Crash 7: `Named export 'ReactNode' not found` ``` SyntaxError: Named export 'ReactNode' not found. The requested module 'react' is a CommonJS module ``` esbuild (Vite's default TS transform) cannot determine that `import { ReactNode } from "react"` is type-only without full TypeScript type information. It preserves the import, and Vite's module runner fails because the pre-bundled CJS `react` module only has runtime exports. ## Solution Two new Vite plugins, registered in vinext's plugin array: ### Plugin 1: `vinext:patch-react-server-dom` Applies 6 targeted string-replacement patches to `react-server-dom-webpack` dev builds at transform time: | # | Patch | Technique | |---|-------|-----------| | 1 | `null !== task.debugStack` → `null != task.debugStack` | Loose inequality catches both `null` and `undefined` | | 2 | `parseStackTrace(error, skipFrames)` | Early return `[]` when `error == null` | | 3 | `supportsCreateTask = !!console.createTask` | Set to `false` | | 4 | `buildFakeCallStack(response, stack, ...)` | Early return `innerCall` when `!stack` | | 5 | `resolveErrorDev(response, errorInfo)` | Replace with simple Error constructor preserving metadata | | 6 | `throw Error("...without development properties...")` | Replace with `void 0` | Targets both standalone `react-server-dom-webpack` and the vendor copy in `@vitejs/plugin-rsc/dist/vendor/react-server-dom/`. ### Plugin 2: `vinext:strip-react-type-imports` Maintains the complete set of React 19 runtime exports. For `import { ... } from "react"` statements, strips any specifier not in the runtime set: ```typescript // Before (esbuild preserves this): import { useState, ReactNode, useEffect } from "react" // After (plugin strips ReactNode): import { useState, useEffect } from "react" ``` Handles edge cases: - `import type { ... }` → skipped entirely - `import { type ReactNode, useState }` → inline `type` annotation respected - `import { ReactNode as RN }` → aliases resolved correctly - `import { ReactNode }` → replaced with comment placeholder ## Changes | File | Change | |------|--------| | `packages/vinext/src/plugins/patch-react-server-dom.ts` | New plugin (125 lines) | | `packages/vinext/src/plugins/strip-react-type-imports.ts` | New plugin (67 lines) | | `packages/vinext/src/index.ts` | Import + register both plugins | ## Reproduction Any app using React 19 with vinext in development mode: ```bash npx create-next-app@latest my-app cd my-app npm install vinext # Add vinext to vite.config.ts npm run dev # → Multiple crashes from react-server-dom-webpack dev builds ``` ## Risk Low. Both plugins: - Only match development builds (`.development.` in filename) - Use `enforce: "pre"` so they run before other transforms - Only apply when the target strings are found (no-op on production builds) - Are idempotent (safe to re-run) The `strip-react-type-imports` plugin is conservative — it only strips names that are **not** in the React 19 runtime export list. If React adds new runtime exports in the future, the allowlist needs to be updated. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:57 +02:00
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#591
No description provided.