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
6 changes: 5 additions & 1 deletion packages/plugin-rsc/examples/basic/src/server.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { handleRequest } from './framework/entry.rsc.tsx'
import './styles.css'

export default async function handler(request: Request): Promise<Response> {
async function handler(request: Request): Promise<Response> {
const url = new URL(request.url)
const { Root } = await import('./routes/root.tsx')
const nonce = !process.env.NO_CSP ? crypto.randomUUID() : undefined
Expand Down Expand Up @@ -38,6 +38,10 @@ export default async function handler(request: Request): Promise<Response> {
return response
}

export default {
fetch: handler,
}

if (import.meta.hot) {
import.meta.hot.accept()
}
7 changes: 5 additions & 2 deletions packages/plugin-rsc/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
getEntrySource,
hashString,
normalizeRelativePath,
getFetchHandlerExport,
sortObject,
withRollupError,
} from './plugins/utils'
Expand Down Expand Up @@ -513,13 +514,14 @@ export default function vitePluginRsc(
`[vite-rsc] failed to resolve server handler '${source}'`,
)
const mod = await environment.runner.import(resolved.id)
const fetchHandler = getFetchHandlerExport(mod)
// 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)
await createRequestListener(fetchHandler)(req, res)
} catch (e) {
next(e)
}
Expand All @@ -540,7 +542,8 @@ export default function vitePluginRsc(
)
const entry = pathToFileURL(entryFile).href
const mod = await import(/* @vite-ignore */ entry)
const handler = createRequestListener(mod.default)
const fetchHandler = getFetchHandlerExport(mod)
const handler = createRequestListener(fetchHandler)

// disable compressions since it breaks html streaming
// https://github.com/vitejs/vite/blob/9f5c59f07aefb1756a37bcb1c0aff24d54288950/packages/vite/src/node/preview.ts#L178
Expand Down
23 changes: 23 additions & 0 deletions packages/plugin-rsc/src/plugins/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,26 @@ export function getEntrySource(
export function hashString(v: string): string {
return createHash('sha256').update(v).digest().toString('hex').slice(0, 12)
}

// normalize server entry exports to align with server runtimes
// https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/
// https://srvx.h3.dev/guide
// https://vercel.com/docs/functions/functions-api-reference?framework=other#fetch-web-standard
// https://github.com/jacob-ebey/rsbuild-rsc-playground/blob/eb1a54afa49cbc5ff93c315744d7754d5ed63498/plugin/fetch-server.ts#L59-L79
export function getFetchHandlerExport(exports: object): any {
if ('default' in exports) {
const default_ = exports.default
if (
default_ &&
typeof default_ === 'object' &&
'fetch' in default_ &&
typeof default_.fetch === 'function'
) {
return default_.fetch
}
if (typeof default_ === 'function') {
return default_
}
}
throw new Error('Invalid server handler entry')
}
Loading