From 7747f53cb6c37a295cc626e8de628a14d2fb7ec3 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Mon, 6 Jul 2026 00:13:00 -0400 Subject: [PATCH] feat(plugin-rsc): expose Node.js stream APIs (renderToPipeableStream, createFromNodeStream) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-export Node.js-native stream variants from react-server-dom-webpack as new entry points (@vitejs/plugin-rsc/rsc.node, ssr.node) without duplicating existing code — each .node file re-exports from its edge counterpart and adds only the Node-specific APIs. Closes #1162 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/plugin-rsc/src/react/rsc.node.ts | 25 ++++++++++++++++ packages/plugin-rsc/src/react/ssr.node.ts | 16 +++++++++++ packages/plugin-rsc/src/rsc.node.tsx | 35 +++++++++++++++++++++++ packages/plugin-rsc/src/ssr.node.tsx | 3 ++ packages/plugin-rsc/src/types/index.ts | 9 ++++++ packages/plugin-rsc/tsdown.config.ts | 4 +++ 6 files changed, 92 insertions(+) create mode 100644 packages/plugin-rsc/src/react/rsc.node.ts create mode 100644 packages/plugin-rsc/src/react/ssr.node.ts create mode 100644 packages/plugin-rsc/src/rsc.node.tsx create mode 100644 packages/plugin-rsc/src/ssr.node.tsx diff --git a/packages/plugin-rsc/src/react/rsc.node.ts b/packages/plugin-rsc/src/react/rsc.node.ts new file mode 100644 index 000000000..3ba5ebba7 --- /dev/null +++ b/packages/plugin-rsc/src/react/rsc.node.ts @@ -0,0 +1,25 @@ +// @ts-ignore +import * as ReactServerNode from '@vitejs/plugin-rsc/vendor/react-server-dom/server.node' +import { createClientManifest } from '../core/rsc' +import type { PipeableStream, RenderToPipeableStreamOptions } from '../types' + +export * from './rsc' + +export function renderToPipeableStream( + data: T, + options?: RenderToPipeableStreamOptions, + extraOptions?: { + /** + * @internal + */ + onClientReference?: (metadata: { id: string; name: string }) => void + }, +): PipeableStream { + return ReactServerNode.renderToPipeableStream( + data, + createClientManifest({ + onClientReference: extraOptions?.onClientReference, + }), + options, + ) +} diff --git a/packages/plugin-rsc/src/react/ssr.node.ts b/packages/plugin-rsc/src/react/ssr.node.ts new file mode 100644 index 000000000..90e71abe6 --- /dev/null +++ b/packages/plugin-rsc/src/react/ssr.node.ts @@ -0,0 +1,16 @@ +// @ts-ignore +import * as ReactClientNode from '@vitejs/plugin-rsc/vendor/react-server-dom/client.node' +import { createServerConsumerManifest } from '../core/ssr' +import type { CreateFromNodeStreamOptions } from '../types' + +export * from './ssr' + +export function createFromNodeStream( + stream: import('node:stream').Readable, + options: CreateFromNodeStreamOptions = {}, +): Promise { + return ReactClientNode.createFromNodeStream(stream, { + serverConsumerManifest: createServerConsumerManifest(), + ...options, + }) +} diff --git a/packages/plugin-rsc/src/rsc.node.tsx b/packages/plugin-rsc/src/rsc.node.tsx new file mode 100644 index 000000000..4e81ed601 --- /dev/null +++ b/packages/plugin-rsc/src/rsc.node.tsx @@ -0,0 +1,35 @@ +import assetsManifest from 'virtual:vite-rsc/assets-manifest' +import type { ResolvedAssetDeps } from './plugin' +import { renderToPipeableStream as originalRenderToPipeableStream } from './react/rsc.node' +import type { PipeableStream } from './types' + +export * from './rsc' + +export function renderToPipeableStream( + data: T, + options?: object, + extraOptions?: { + /** + * @experimental + */ + onClientReference?: (metadata: { + id: string + name: string + deps: ResolvedAssetDeps + }) => void + }, +): PipeableStream { + return originalRenderToPipeableStream(data, options, { + onClientReference(metadata) { + const deps = assetsManifest.clientReferenceDeps[metadata.id] ?? { + js: [], + css: [], + } + extraOptions?.onClientReference?.({ + id: metadata.id, + name: metadata.name, + deps, + }) + }, + }) +} diff --git a/packages/plugin-rsc/src/ssr.node.tsx b/packages/plugin-rsc/src/ssr.node.tsx new file mode 100644 index 000000000..d4a1dfe29 --- /dev/null +++ b/packages/plugin-rsc/src/ssr.node.tsx @@ -0,0 +1,3 @@ +export * from './ssr' + +export { createFromNodeStream } from './react/ssr.node' diff --git a/packages/plugin-rsc/src/types/index.ts b/packages/plugin-rsc/src/types/index.ts index bb827a7b4..a723d7539 100644 --- a/packages/plugin-rsc/src/types/index.ts +++ b/packages/plugin-rsc/src/types/index.ts @@ -93,6 +93,15 @@ type DebugChannel = { writable?: WritableStream } +export interface PipeableStream { + pipe(destination: T): T + abort(reason?: unknown): void +} + +export type RenderToPipeableStreamOptions = RenderToReadableStreamOptions + +export type CreateFromNodeStreamOptions = CreateFromReadableStreamEdgeOptions + // TODO: for now keep them unknown // export type ServerTemporaryReferenceSet = WeakMap // export type ClientTemporaryReferenceSet = Map diff --git a/packages/plugin-rsc/tsdown.config.ts b/packages/plugin-rsc/tsdown.config.ts index d226dd01d..f51b16f80 100644 --- a/packages/plugin-rsc/tsdown.config.ts +++ b/packages/plugin-rsc/tsdown.config.ts @@ -7,14 +7,18 @@ export default defineConfig({ 'src/plugin.ts', 'src/browser.ts', 'src/ssr.tsx', + 'src/ssr.node.tsx', 'src/rsc.tsx', + 'src/rsc.node.tsx', 'src/core/browser.ts', 'src/core/ssr.ts', 'src/core/rsc.ts', 'src/core/plugin.ts', 'src/react/browser.ts', 'src/react/ssr.ts', + 'src/react/ssr.node.ts', 'src/react/rsc.ts', + 'src/react/rsc.node.ts', 'src/transforms/index.ts', 'src/plugins/cjs.ts', 'src/utils/rpc.ts',