Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/path-addressed-server-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/vite-plugin": minor
---

Route path-addressed server function calls (solidjs/solid#3076). A call's address is now `<endpoint>/<id>` with arguments in the query, so the dev middleware and the generated request-dispatch gate match the endpoint by mount prefix instead of exact pathname, and the dev middleware's module-preload id comes from the path segment. The retired `X-Server-Function-Id` header and `?id=` forms remain as transitional fallbacks for the RC window only — they will be dropped before the stable release.
37 changes: 30 additions & 7 deletions src/server-functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,27 +515,50 @@ export function serverFunctions(
if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) {
return;
}
// A call's address is `<endpoint>/<id>` (solidjs/solid#3076) — the
// mount plus exactly one path segment. Bare-mount requests still
// reach the runtime handler (it answers 404), so misdirected posts
// fail through the endpoint rather than falling through to SSR.
const underMount = (pathname: string, mount: string) =>
pathname === mount || pathname.startsWith(mount + '/');
server.middlewares.use((req, res, next) => {
const url = new URL(req.url || '/', 'http://localhost');
// Match with and without `base` — middleware-mode hosts may mount
// vite.middlewares below the base themselves.
if (url.pathname !== resolvedEndpoint && url.pathname !== endpoint) {
if (!underMount(url.pathname, resolvedEndpoint) && !underMount(url.pathname, endpoint)) {
return next();
}
const basePrefixed = underMount(url.pathname, resolvedEndpoint);
// When the stripped form matched, restore the base for dispatch:
// the generated handler compares the request pathname against the
// base-prefixed endpoint, and production handlers only ever see
// base-prefixed URLs.
const dispatchUrl =
url.pathname === resolvedEndpoint ? undefined : joinBase(base, req.url || '/');
const dispatchUrl = basePrefixed ? undefined : joinBase(base, req.url || '/');
(async () => {
// Make sure the referenced module has been evaluated in the SSR
// environment so its registration exists — functions only client
// code references are never loaded by the SSR render itself.
const headerId = req.headers['x-server-function-id'];
const functionId =
(typeof headerId === 'string' ? headerId.split('#')[0] : undefined) ||
url.searchParams.get('id');
// The id lives in the path segment after the mount.
const mount = basePrefixed ? resolvedEndpoint : endpoint;
const segment = url.pathname.slice(mount.length + 1);
let functionId: string | null = null;
if (segment && !segment.includes('/')) {
try {
functionId = decodeURIComponent(segment);
} catch {
// not an address; the runtime handler answers the 404
}
}
if (!functionId) {
// TRANSITIONAL (remove before 3.0 stable): the retired header
// and `?id=` addressing, kept only for the RC window where this
// plugin meets a @solidjs/web older than the path-addressing
// change (solidjs/solid#3076).
const headerId = req.headers['x-server-function-id'];
functionId =
(typeof headerId === 'string' ? headerId.split('#')[0] : undefined) ||
url.searchParams.get('id');
}
if (functionId) {
const entry = moduleForFunctionId(functionId);
if (entry) await ssrEnvironment.runner.import(moduleDevUrl(entry));
Expand Down
6 changes: 5 additions & 1 deletion src/ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,11 @@ export function startServe(
lines.push(``, `async function dispatchRequest(request, event, options) {`);
if (composeServerFunctions) {
lines.push(
` if (new URL(request.url).pathname === endpoint) {`,
// A call's address is `<endpoint>/<id>` (solidjs/solid#3076); the
// bare mount still routes so a misaddressed request 404s through the
// runtime handler instead of rendering a page at it.
` const requestPath = new URL(request.url).pathname;`,
` if (requestPath === endpoint || requestPath.startsWith(endpoint + '/')) {`,
// The call shares the middleware chain's event (locals decoration,
// the response stub); an explicit host-provided createEvent wins.
// No fold here: the runtime's server-function handler runs the
Expand Down
Loading