diff --git a/.changeset/path-addressed-server-functions.md b/.changeset/path-addressed-server-functions.md new file mode 100644 index 0000000..d92dd4f --- /dev/null +++ b/.changeset/path-addressed-server-functions.md @@ -0,0 +1,5 @@ +--- +"@solidjs/vite-plugin": minor +--- + +Route path-addressed server function calls (solidjs/solid#3076). A call's address is now `/` 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. diff --git a/src/server-functions/index.ts b/src/server-functions/index.ts index faeac2f..49c1cf3 100644 --- a/src/server-functions/index.ts +++ b/src/server-functions/index.ts @@ -515,27 +515,50 @@ export function serverFunctions( if (internal.externalDevServer || !isRunnableEnvironment(ssrEnvironment)) { return; } + // A call's address is `/` (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)); diff --git a/src/ssr/index.ts b/src/ssr/index.ts index 14d700d..d2609d5 100644 --- a/src/ssr/index.ts +++ b/src/ssr/index.ts @@ -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 `/` (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