diff --git a/packages/plugin-rsc/e2e/base.test.ts b/packages/plugin-rsc/e2e/base.test.ts index 6ab6cc3d..1f651b9d 100644 --- a/packages/plugin-rsc/e2e/base.test.ts +++ b/packages/plugin-rsc/e2e/base.test.ts @@ -1,5 +1,5 @@ -import { test } from '@playwright/test' -import { setupInlineFixture, useFixture } from './fixture' +import { expect, test } from '@playwright/test' +import { setupInlineFixture, useFixture, type Fixture } from './fixture' import { defineStarterTest } from './starter' test.describe(() => { @@ -27,17 +27,31 @@ test.describe(() => { test.describe('dev-base', () => { const f = useFixture({ root, mode: 'dev' }) - defineStarterTest({ + const f2: Fixture = { ...f, url: (url) => new URL(url ?? './', f.url('./custom-base/')).href, - }) + } + defineStarterTest(f2) + testRequestUrl(f2) }) test.describe('build-base', () => { const f = useFixture({ root, mode: 'build' }) - defineStarterTest({ + const f2: Fixture = { ...f, url: (url) => new URL(url ?? './', f.url('./custom-base/')).href, - }) + } + defineStarterTest(f2) + testRequestUrl(f2) }) + + function testRequestUrl(f: Fixture) { + test('request url', async ({ page }) => { + await page.goto(f.url()) + await page.waitForSelector('#root') + await expect(page.locator('.card').nth(2)).toHaveText( + `Request URL: ${f.url()}`, + ) + }) + } }) diff --git a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx index 5f346b56..fa1c2784 100644 --- a/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx +++ b/packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx @@ -58,14 +58,18 @@ export default async function handler(request: Request): Promise { // we render RSC stream after handling server function request // so that new render reflects updated state from server function call // to achieve single round trip to mutate and fetch from server. - const rscPayload: RscPayload = { root: , formState, returnValue } + const url = new URL(request.url) + const rscPayload: RscPayload = { + root: , + formState, + returnValue, + } const rscOptions = { temporaryReferences } const rscStream = renderToReadableStream(rscPayload, rscOptions) // respond RSC stream without HTML rendering based on framework's convention. // here we use request header `content-type`. // additionally we allow `?__rsc` and `?__html` to easily view payload directly. - const url = new URL(request.url) const isRscRequest = (!request.headers.get('accept')?.includes('text/html') && !url.searchParams.has('__html')) || diff --git a/packages/plugin-rsc/examples/starter/src/root.tsx b/packages/plugin-rsc/examples/starter/src/root.tsx index a42c6a92..c6a64970 100644 --- a/packages/plugin-rsc/examples/starter/src/root.tsx +++ b/packages/plugin-rsc/examples/starter/src/root.tsx @@ -4,7 +4,7 @@ import { getServerCounter, updateServerCounter } from './action.tsx' import reactLogo from './assets/react.svg' import { ClientCounter } from './client.tsx' -export function Root() { +export function Root(props: { url: URL }) { return ( @@ -14,13 +14,13 @@ export function Root() { Vite + RSC - + ) } -function App() { +function App(props: { url: URL }) { return (
@@ -43,6 +43,7 @@ function App() {
+
Request URL: {props.url?.href}
  • Edit src/client.tsx to test client HMR. @@ -52,15 +53,15 @@ function App() {
  • Visit{' '} - - /?__rsc + + ?__rsc {' '} to view RSC stream payload.
  • Visit{' '} - - /?__nojs + + ?__nojs {' '} to test server action without js enabled.
  • diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 6366d940..d00db1ad 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -471,6 +471,10 @@ export default function vitePluginRsc( `[vite-rsc] failed to resolve server handler '${source}'`, ) const mod = await environment.runner.import(resolved.id) + // expose original request url to server handler. + // for example, this restores `base` which is automatically stripped by Vite. + // https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/server/middlewares/base.ts#L18-L20 + req.url = req.originalUrl ?? req.url // ensure catching rejected promise // https://github.com/mjackson/remix-the-web/blob/b5aa2ae24558f5d926af576482caf6e9b35461dc/packages/node-fetch-server/src/lib/request-listener.ts#L87 await createRequestListener(mod.default)(req, res) @@ -506,6 +510,7 @@ export default function vitePluginRsc( return () => { server.middlewares.use(async (req, res, next) => { try { + req.url = req.originalUrl ?? req.url await handler(req, res) } catch (e) { next(e)