-
-
Notifications
You must be signed in to change notification settings - Fork 266
feat(rsc): ability to not load server reference module during createFromReadableStream on rsc environment
#1289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a976ade
fix(plugin-rsc): preserve server references during SSR
hi-ogawa 7fdad30
fix(plugin-rsc): make server reference preservation opt-in
hi-ogawa 252ac0b
Merge branch 'main' into rsc-preserve-server-references
hi-ogawa bde0b30
fix(plugin-rsc): support replayed references in dev
hi-ogawa 1c85e2a
refactor(plugin-rsc): align replay example with starter
hi-ogawa ee83851
refactor(plugin-rsc): simplify replay example
hi-ogawa 9509b52
refactor(plugin-rsc): track replay action state
hi-ogawa 1be7726
refactor(plugin-rsc): clarify replay example flow
hi-ogawa b082cd4
test(plugin-rsc): cover cache replay across restarts
hi-ogawa 533c467
test(plugin-rsc): compare cache replay modes
hi-ogawa af13f9e
test(plugin-rsc): stabilize cache replay assertions
hi-ogawa 117fe44
docs(plugin-rsc): unify cache replay instructions
hi-ogawa 07a82ea
docs(plugin-rsc): explain preserved server references
hi-ogawa 3e2b533
test(plugin-rsc): explain restart navigation
hi-ogawa cdcdfe3
refactor(plugin-rsc): rename preserved reference tag
hi-ogawa cb1c306
fix(plugin-rsc): respect fs restrictions during reference validation
hi-ogawa 69a4b60
nit
hi-ogawa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
packages/plugin-rsc/examples/rsc-server-reference-replay/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .flight-cache | ||
| dist |
46 changes: 46 additions & 0 deletions
46
packages/plugin-rsc/examples/rsc-server-reference-replay/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Persisted Flight server reference replay | ||
|
|
||
| This example persists a Flight payload containing a server reference, restarts the server, and replays the payload without importing the server action in the RSC environment. The action is imported only when the replayed form invokes it. | ||
|
|
||
| The example intentionally uses a native form without JavaScript so the final step exercises `decodeAction`. | ||
|
|
||
| ## Development manual test | ||
|
|
||
| Start the first process: | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| 1. Visit `http://localhost:5173/cache` and confirm the page displays `true`. | ||
| 2. Stop the development server without changing the source graph. | ||
| 3. Run `pnpm dev` again. | ||
| 4. Visit `http://localhost:5173/` and confirm the page displays `false`. | ||
| 5. Select **Invoke replayed action** and confirm the response displays `true`. | ||
|
|
||
| The source graph must remain unchanged across the restart so its development server-reference IDs remain stable. | ||
|
|
||
| ## Production manual test | ||
|
|
||
| Build the example once: | ||
|
|
||
| ```bash | ||
| pnpm build | ||
| ``` | ||
|
|
||
| Start the first process: | ||
|
|
||
| ```bash | ||
| pnpm preview | ||
| ``` | ||
|
|
||
| 1. Visit `http://localhost:4173/cache`. | ||
| 2. Confirm the page displays `Action imported in the RSC environment: true`. | ||
| 3. Stop the preview server without rebuilding. | ||
| 4. Run `pnpm preview` again. | ||
| 5. Visit `http://localhost:4173/` and confirm the page displays `false`. | ||
| 6. Select **Invoke replayed action** and confirm the response displays `true`. | ||
|
|
||
| The same production build must be used for both processes because the persisted Flight payload contains build-specific server-reference IDs. | ||
|
|
||
| Delete `.flight-cache` to reset the example. |
22 changes: 22 additions & 0 deletions
22
packages/plugin-rsc/examples/rsc-server-reference-replay/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "name": "@vitejs/plugin-rsc-examples-rsc-server-reference-replay", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "license": "MIT", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "preview": "vite preview" | ||
| }, | ||
| "dependencies": { | ||
| "react": "^19.2.7", | ||
| "react-dom": "^19.2.7" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^19.2.17", | ||
| "@types/react-dom": "^19.2.3", | ||
| "@vitejs/plugin-rsc": "latest", | ||
| "vite": "^8.1.4" | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
packages/plugin-rsc/examples/rsc-server-reference-replay/src/action-import-state.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| let actionImported = false | ||
|
|
||
| export function markActionImported() { | ||
| actionImported = true | ||
| } | ||
|
|
||
| export function wasActionImported() { | ||
| return actionImported | ||
| } | ||
7 changes: 7 additions & 0 deletions
7
packages/plugin-rsc/examples/rsc-server-reference-replay/src/action.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| 'use server' | ||
|
|
||
| import { markActionImported } from './action-import-state' | ||
|
|
||
| markActionImported() | ||
|
|
||
| export async function replayedAction() {} | ||
|
hi-ogawa marked this conversation as resolved.
Outdated
|
||
9 changes: 9 additions & 0 deletions
9
packages/plugin-rsc/examples/rsc-server-reference-replay/src/cached-content.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { replayedAction } from './action' | ||
|
|
||
| export function CachedContent() { | ||
| return ( | ||
| <form action={replayedAction}> | ||
| <button type="submit">Invoke replayed action</button> | ||
| </form> | ||
| ) | ||
| } |
1 change: 1 addition & 0 deletions
1
packages/plugin-rsc/examples/rsc-server-reference-replay/src/framework/entry.browser.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| // This example only tests progressive enhancement without JavaScript. | ||
|
hi-ogawa marked this conversation as resolved.
Outdated
|
||
71 changes: 71 additions & 0 deletions
71
packages/plugin-rsc/examples/rsc-server-reference-replay/src/framework/entry.rsc.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { readFile, writeFile } from 'node:fs/promises' | ||
| import { resolve } from 'node:path' | ||
| import { | ||
| createFromReadableStream, | ||
| decodeAction, | ||
| renderToReadableStream, | ||
| } from '@vitejs/plugin-rsc/rsc' | ||
| import { wasActionImported } from '../action-import-state' | ||
| import { Root } from '../root' | ||
|
|
||
| const cacheFile = resolve('.flight-cache') | ||
|
|
||
| async function handler(request: Request): Promise<Response> { | ||
| const url = new URL(request.url) | ||
|
|
||
| if (request.method === 'POST') { | ||
| const action = await decodeAction(await request.formData()) | ||
| await action() | ||
| } | ||
|
|
||
| let bytes: Uint8Array | ||
| if (url.pathname === '/cache') { | ||
| const { CachedContent } = await import('../cached-content') | ||
| const stream = renderToReadableStream(<CachedContent />) | ||
| bytes = new Uint8Array(await new Response(stream).arrayBuffer()) | ||
|
hi-ogawa marked this conversation as resolved.
Outdated
|
||
| await writeFile(cacheFile, bytes) | ||
| } else { | ||
| try { | ||
| bytes = await readFile(cacheFile) | ||
| } catch (error) { | ||
| if ((error as NodeJS.ErrnoException).code === 'ENOENT') { | ||
| return new Response('Visit /cache before replaying the saved Flight.', { | ||
| status: 404, | ||
| }) | ||
| } | ||
| throw error | ||
| } | ||
| } | ||
|
|
||
| const cachedContent = await createFromReadableStream<React.ReactNode>( | ||
| toReadableStream(bytes), | ||
| {}, | ||
| { preserveServerReferences: true }, | ||
| ) | ||
| const rscStream = renderToReadableStream({ | ||
| root: ( | ||
| <Root | ||
| actionImported={wasActionImported()} | ||
| cachedContent={cachedContent} | ||
| /> | ||
| ), | ||
| }) | ||
| const ssr = await import.meta.viteRsc.loadModule< | ||
| typeof import('./entry.ssr') | ||
| >('ssr', 'index') | ||
| const htmlStream = await ssr.renderHtml(rscStream) | ||
| return new Response(htmlStream, { | ||
| headers: { 'content-type': 'text/html;charset=utf-8' }, | ||
| }) | ||
| } | ||
|
|
||
| function toReadableStream(bytes: Uint8Array): ReadableStream<Uint8Array> { | ||
| return new ReadableStream({ | ||
| start(controller) { | ||
| controller.enqueue(bytes) | ||
| controller.close() | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| export default { fetch: handler } | ||
17 changes: 17 additions & 0 deletions
17
packages/plugin-rsc/examples/rsc-server-reference-replay/src/framework/entry.ssr.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { createFromReadableStream } from '@vitejs/plugin-rsc/ssr' | ||
| import React from 'react' | ||
| import { renderToReadableStream } from 'react-dom/server.edge' | ||
|
|
||
| type RscPayload = { root: React.ReactNode } | ||
|
|
||
| export async function renderHtml( | ||
| rscStream: ReadableStream<Uint8Array>, | ||
| ): Promise<ReadableStream<Uint8Array>> { | ||
| let payload: Promise<RscPayload> | undefined | ||
| function SsrRoot() { | ||
| payload ??= createFromReadableStream<RscPayload>(rscStream) | ||
| return React.use(payload).root | ||
| } | ||
|
|
||
| return renderToReadableStream(<SsrRoot />) | ||
| } |
19 changes: 19 additions & 0 deletions
19
packages/plugin-rsc/examples/rsc-server-reference-replay/src/root.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| export function Root(props: { | ||
| actionImported: boolean | ||
| cachedContent: React.ReactNode | ||
| }) { | ||
| return ( | ||
| <html> | ||
| <body> | ||
| <h1>Persisted Flight server reference</h1> | ||
| <p> | ||
| Action imported in the RSC environment:{' '} | ||
| <output data-testid="action-imported"> | ||
| {String(props.actionImported)} | ||
| </output> | ||
| </p> | ||
| {props.cachedContent} | ||
| </body> | ||
| </html> | ||
| ) | ||
| } |
17 changes: 17 additions & 0 deletions
17
packages/plugin-rsc/examples/rsc-server-reference-replay/tsconfig.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "erasableSyntaxOnly": true, | ||
| "allowImportingTsExtensions": true, | ||
| "noUnusedLocals": true, | ||
| "noUnusedParameters": true, | ||
| "skipLibCheck": true, | ||
| "verbatimModuleSyntax": true, | ||
| "noEmit": true, | ||
| "moduleResolution": "Bundler", | ||
| "module": "ESNext", | ||
| "target": "ESNext", | ||
| "lib": ["ESNext", "DOM"], | ||
| "types": ["vite/client", "@vitejs/plugin-rsc/types"], | ||
| "jsx": "react-jsx" | ||
| } | ||
| } |
29 changes: 29 additions & 0 deletions
29
packages/plugin-rsc/examples/rsc-server-reference-replay/vite.config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import rsc from '@vitejs/plugin-rsc' | ||
| import { defineConfig } from 'vite' | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [rsc()], | ||
| environments: { | ||
| rsc: { | ||
| build: { | ||
| rollupOptions: { | ||
| input: { index: './src/framework/entry.rsc.tsx' }, | ||
| }, | ||
| }, | ||
| }, | ||
| ssr: { | ||
| build: { | ||
| rollupOptions: { | ||
| input: { index: './src/framework/entry.ssr.tsx' }, | ||
| }, | ||
| }, | ||
| }, | ||
| client: { | ||
| build: { | ||
| rollupOptions: { | ||
| input: { index: './src/framework/entry.browser.ts' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.