diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b356d3d15b..b099a3f854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -553,6 +553,14 @@ jobs: label: app-with-src shardIndex: 1 shardTotal: 1 + - project: app-rsdw-override-prod + label: app-rsdw-override-prod + shardIndex: 1 + shardTotal: 1 + - project: app-rsc-isolated-standalone + label: app-rsc-isolated-standalone + shardIndex: 1 + shardTotal: 1 - project: standalone-output label: standalone-output shardIndex: 1 diff --git a/README.md b/README.md index 28740f63f0..ab08d01a1c 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,13 @@ npm install -D vite @vitejs/plugin-react If you're using the App Router, also install: ```bash -npm install react-server-dom-webpack npm install -D @vitejs/plugin-rsc ``` +`@vitejs/plugin-rsc` includes the React Flight runtime used by default. Projects +that intentionally use a different React release channel can install the +matching `react-server-dom-webpack` version as an explicit runtime override. + Replace `next` with `vinext` in your scripts: ```json @@ -768,7 +771,7 @@ Analysis of the build output shows two main factors: 1. **Tree-shaking**: Vite/Rolldown produces a smaller React+ReactDOM bundle than Next.js/Turbopack. Rolldown's more aggressive dead-code elimination accounts for roughly half the overall difference. 2. **Framework overhead**: Next.js ships more client-side infrastructure (router, Turbopack runtime loader, prefetching, error handling) than vinext's lighter client runtime. -Both frameworks ship the same app code and the same RSC client runtime (`react-server-dom-webpack`). The difference is in how much of React's internals survive tree-shaking and how much framework plumbing each tool adds. +Both frameworks ship the same app code and the React Flight client runtime. Vinext receives that runtime from `@vitejs/plugin-rsc`; Next.js carries its own compiled copy. The difference is in how much of React's internals survive tree-shaking and how much framework plumbing each tool adds. @@ -892,7 +895,7 @@ Or add it to your `package.json` as a file dependency: } ``` -vinext has peer dependencies on `react ^19.2.6`, `react-dom ^19.2.6`, `react-server-dom-webpack ^19.2.6`, and `vite ^8.0.0`. Then replace `next` with `vinext` in your scripts and run as normal. +vinext has peer dependencies on `react ^19.2.6`, `react-dom ^19.2.6`, and `vite ^8.0.0`. App Router projects also use the optional `@vitejs/plugin-rsc` peer, which provides the default Flight runtime. Its vendored runtime requires React 19.2.8 or newer; `vinext init` upgrades older stable React versions unless the project declares a matching `react-server-dom-webpack` override. Then replace `next` with `vinext` in your scripts and run as normal. ## Contributing diff --git a/apps/web/package.json b/apps/web/package.json index 521fba6a1e..c3eed6162d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -36,7 +36,6 @@ "@vitejs/plugin-rsc": "catalog:", "drizzle-kit": "catalog:", "postcss": "catalog:", - "react-server-dom-webpack": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", diff --git a/benchmarks/vinext/package.json b/benchmarks/vinext/package.json index d041fbc637..7a115e1e87 100644 --- a/benchmarks/vinext/package.json +++ b/benchmarks/vinext/package.json @@ -11,7 +11,6 @@ "@vitejs/plugin-react": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-server-dom-webpack": "catalog:", "vinext": "workspace:*" }, "devDependencies": { diff --git a/examples/app-router-cloudflare/package.json b/examples/app-router-cloudflare/package.json index 79257b252c..d79dd355dd 100644 --- a/examples/app-router-cloudflare/package.json +++ b/examples/app-router-cloudflare/package.json @@ -15,7 +15,6 @@ "vinext": "workspace:*", "@vinext/cloudflare": "workspace:*", "@vitejs/plugin-rsc": "catalog:", - "react-server-dom-webpack": "catalog:", "@cloudflare/vite-plugin": "catalog:", "wrangler": "catalog:" }, diff --git a/examples/app-router-playground/package.json b/examples/app-router-playground/package.json index cf78f99c64..938a909946 100644 --- a/examples/app-router-playground/package.json +++ b/examples/app-router-playground/package.json @@ -20,7 +20,6 @@ "ms": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-server-dom-webpack": "catalog:", "recma-codehike": "catalog:", "remark-codehike": "catalog:", "use-count-up": "catalog:", diff --git a/examples/benchmarks/package.json b/examples/benchmarks/package.json index 0bd89ad778..f322a76ecb 100644 --- a/examples/benchmarks/package.json +++ b/examples/benchmarks/package.json @@ -13,7 +13,6 @@ "vite": "catalog:", "vinext": "workspace:*", "@vitejs/plugin-rsc": "catalog:", - "react-server-dom-webpack": "catalog:", "@cloudflare/vite-plugin": "catalog:", "@cloudflare/kumo": "catalog:", "@phosphor-icons/react": "catalog:", diff --git a/examples/fumadocs-docs-template/package.json b/examples/fumadocs-docs-template/package.json index a9c469af77..8127d791df 100644 --- a/examples/fumadocs-docs-template/package.json +++ b/examples/fumadocs-docs-template/package.json @@ -31,7 +31,6 @@ "@vitejs/plugin-react": "catalog:", "@vitejs/plugin-rsc": "catalog:", "postcss": "catalog:", - "react-server-dom-webpack": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", "vinext": "workspace:*", diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 8a7ddb2f3d..cacd1e8846 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -17,7 +17,6 @@ "vite": "catalog:", "vinext": "workspace:*", "@vitejs/plugin-rsc": "catalog:", - "react-server-dom-webpack": "catalog:", "@cloudflare/vite-plugin": "catalog:", "wrangler": "catalog:" }, diff --git a/examples/nextra-docs-template/package.json b/examples/nextra-docs-template/package.json index 29da87c08c..aab4b6d2c6 100644 --- a/examples/nextra-docs-template/package.json +++ b/examples/nextra-docs-template/package.json @@ -11,7 +11,6 @@ "@vitejs/plugin-react": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-server-dom-webpack": "catalog:", "vinext": "workspace:*" }, "devDependencies": { diff --git a/examples/static-export/package.json b/examples/static-export/package.json index 5eff90566f..48c8372018 100644 --- a/examples/static-export/package.json +++ b/examples/static-export/package.json @@ -12,7 +12,6 @@ "@vitejs/plugin-rsc": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-server-dom-webpack": "catalog:", "vinext": "workspace:*", "vite": "catalog:", "wrangler": "catalog:" diff --git a/examples/tpr-demo/package.json b/examples/tpr-demo/package.json index 3bd316720d..2f99e5f342 100644 --- a/examples/tpr-demo/package.json +++ b/examples/tpr-demo/package.json @@ -14,7 +14,6 @@ "@vitejs/plugin-rsc": "catalog:", "react": "catalog:", "react-dom": "catalog:", - "react-server-dom-webpack": "catalog:", "vite": "catalog:", "vinext": "workspace:*" }, diff --git a/examples/workers-cache/package.json b/examples/workers-cache/package.json index 43f70ab057..13e00272e8 100644 --- a/examples/workers-cache/package.json +++ b/examples/workers-cache/package.json @@ -15,7 +15,6 @@ "vinext": "workspace:*", "@vinext/cloudflare": "workspace:*", "@vitejs/plugin-rsc": "catalog:", - "react-server-dom-webpack": "catalog:", "@cloudflare/vite-plugin": "catalog:", "wrangler": "catalog:" }, diff --git a/packages/vinext/package.json b/packages/vinext/package.json index 3077902573..10911575f1 100644 --- a/packages/vinext/package.json +++ b/packages/vinext/package.json @@ -196,7 +196,6 @@ "am-i-vibing": "catalog:", "image-size": "catalog:", "pathslash": "catalog:", - "react-server-dom-webpack": "catalog:", "vite": "catalog:", "vite-plus": "catalog:" }, @@ -204,9 +203,8 @@ "@mdx-js/rollup": "^3.0.0", "@vitejs/plugin-react": "^5.1.4 || ^6.0.0", "@vitejs/plugin-rsc": "^0.5.34", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-server-dom-webpack": "^19.2.6", + "react": "^19.2.6 || >=19.3.0-0 <19.4.0 || >=0.0.0-0 <0.0.1", + "react-dom": "^19.2.6 || >=19.3.0-0 <19.4.0 || >=0.0.0-0 <0.0.1", "vite": "^8.0.0" }, "peerDependenciesMeta": { @@ -215,9 +213,6 @@ }, "@vitejs/plugin-rsc": { "optional": true - }, - "react-server-dom-webpack": { - "optional": true } }, "engines": { diff --git a/packages/vinext/src/build/client-build-config.ts b/packages/vinext/src/build/client-build-config.ts index 4d90db2552..43affb1485 100644 --- a/packages/vinext/src/build/client-build-config.ts +++ b/packages/vinext/src/build/client-build-config.ts @@ -213,13 +213,14 @@ export function createClientCodeSplittingConfig( * Splitting React into its own (CSS-free) chunk means global-not-found imports * the framework chunk instead of the layout-bearing entry chunk, so it no * longer inherits the root layout's CSS. The match list mirrors the client - * build's `framework` chunk, plus `react-server-dom-webpack` for the RSC flight - * runtime that the server environment bundles. + * build's `framework` chunk, plus both plugin-rsc's vendored Flight runtime and + * its explicit `react-server-dom-webpack` override path. * * Uses `[\\/]` rather than `/` for the path separator so it matches on Windows * too, per the rolldown `codeSplitting` docs. */ const FRAMEWORK_PACKAGES = ["react", "react-dom", "scheduler", "react-server-dom-webpack"] as const; +const VENDORED_RSC_RUNTIME_PATH = "@vitejs/plugin-rsc/dist/vendor/react-server-dom/"; /** * Regex matching any {@link FRAMEWORK_PACKAGES} package inside `node_modules`. @@ -227,11 +228,14 @@ const FRAMEWORK_PACKAGES = ["react", "react-dom", "scheduler", "react-server-dom * predicate can't drift. */ export const RSC_FRAMEWORK_CHUNK_TEST = new RegExp( - `[\\\\/]node_modules[\\\\/](${FRAMEWORK_PACKAGES.join("|")})[\\\\/]`, + `[\\\\/]node_modules[\\\\/](?:${FRAMEWORK_PACKAGES.join("|")})[\\\\/]|` + + `[\\\\/]node_modules[\\\\/]@vitejs[\\\\/]plugin-rsc[\\\\/]dist[\\\\/]vendor[\\\\/]react-server-dom[\\\\/]`, ); export function isRscFrameworkModule(id: string): boolean { if (!id.includes("node_modules")) return false; + const normalizedId = toSlash(id); + if (normalizedId.includes(`/node_modules/${VENDORED_RSC_RUNTIME_PATH}`)) return true; const pkg = getPackageName(id); return pkg !== null && (FRAMEWORK_PACKAGES as readonly string[]).includes(pkg); } diff --git a/packages/vinext/src/check.ts b/packages/vinext/src/check.ts index c7a3f48ae1..fab32d87e3 100644 --- a/packages/vinext/src/check.ts +++ b/packages/vinext/src/check.ts @@ -1318,7 +1318,7 @@ export function formatReport(result: CheckResult, opts?: { calledFromInit?: bool lines.push(" Or manually:"); lines.push(` 1. Add \x1b[36m"type": "module"\x1b[0m to package.json`); lines.push( - ` 2. Install: \x1b[36m${detectPackageManager(process.cwd())} vinext vite @vitejs/plugin-react${hasAppRouter ? " @vitejs/plugin-rsc react-server-dom-webpack" : ""}\x1b[0m`, + ` 2. Install: \x1b[36m${detectPackageManager(process.cwd())} vinext vite @vitejs/plugin-react${hasAppRouter ? " @vitejs/plugin-rsc" : ""}\x1b[0m`, ); lines.push(` 3. Create vite.config.ts (see docs)`); lines.push(` 4. Run: \x1b[36mnpx vite dev\x1b[0m`); diff --git a/packages/vinext/src/cli.ts b/packages/vinext/src/cli.ts index 831f826266..f77c4e2076 100644 --- a/packages/vinext/src/cli.ts +++ b/packages/vinext/src/cli.ts @@ -24,6 +24,7 @@ import { execFileSync } from "node:child_process"; import { randomBytes } from "node:crypto"; import { detectPackageManager, + detectPackageManagerProductionCommand, ensureViteConfigCompatibility, hasAppDir, hasViteConfig, @@ -577,15 +578,14 @@ async function buildApp() { ? vite.createLogger("info", { allowClearScreen: false }) : createBuildLogger(vite); - // For App Router: upgrade React if needed for react-server-dom-webpack compatibility. - // Without this, builds with older React versions can produce a Worker that crashes at - // runtime with "Cannot read properties of undefined (reading 'moduleMap')". + // Keep App Router's React pair compatible with its Flight runtime. Stable + // React uses plugin-rsc's vendor; prereleases need a matching RSDW override. if (isApp) { const reactUpgrade = getReactUpgradeDeps(process.cwd()); if (reactUpgrade.length > 0) { - const installCmd = detectPackageManager(process.cwd()).replace(/ -D$/, ""); + const installCmd = detectPackageManagerProductionCommand(process.cwd()); const [pm, ...pmArgs] = installCmd.split(" "); - console.log(" Upgrading React for RSC compatibility..."); + console.log(" Installing dependencies for RSC compatibility..."); execFileSync(pm, [...pmArgs, ...reactUpgrade], { cwd: process.cwd(), stdio: "inherit", diff --git a/packages/vinext/src/global.d.ts b/packages/vinext/src/global.d.ts index bcd1b8164a..d5fc751e25 100644 --- a/packages/vinext/src/global.d.ts +++ b/packages/vinext/src/global.d.ts @@ -224,7 +224,7 @@ declare global { * inlined Flight payload kind. * Each `