Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions packages/plugin-rsc/e2e/base.test.ts
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand Down Expand Up @@ -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()}`,
)
})
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ export default async function handler(request: Request): Promise<Response> {
// 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: <Root />, formState, returnValue }
const url = new URL(request.url)
const rscPayload: RscPayload = {
root: <Root url={url} />,
formState,
returnValue,
}
const rscOptions = { temporaryReferences }
const rscStream = renderToReadableStream<RscPayload>(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')) ||
Expand Down
15 changes: 8 additions & 7 deletions packages/plugin-rsc/examples/starter/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<html lang="en">
<head>
Expand All @@ -14,13 +14,13 @@ export function Root() {
<title>Vite + RSC</title>
</head>
<body>
<App />
<App {...props} />
</body>
</html>
)
}

function App() {
function App(props: { url: URL }) {
return (
<div id="root">
<div>
Expand All @@ -43,6 +43,7 @@ function App() {
<button>Server Counter: {getServerCounter()}</button>
</form>
</div>
<div className="card">Request URL: {props.url?.href}</div>
<ul className="read-the-docs">
<li>
Edit <code>src/client.tsx</code> to test client HMR.
Expand All @@ -52,15 +53,15 @@ function App() {
</li>
<li>
Visit{' '}
<a href="/?__rsc" target="_blank">
<code>/?__rsc</code>
<a href="?__rsc" target="_blank">
<code>?__rsc</code>
</a>{' '}
to view RSC stream payload.
</li>
<li>
Visit{' '}
<a href="/?__nojs" target="_blank">
<code>/?__nojs</code>
<a href="?__nojs" target="_blank">
<code>?__nojs</code>
</a>{' '}
to test server action without js enabled.
</li>
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-rsc/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading