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
5 changes: 5 additions & 0 deletions .changeset/bodyless-post-no-body-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/vite-plugin": patch
---

Only attach a request body in the dev middlewares' Node-to-web bridging when the incoming request actually carries one (Content-Length/Transfer-Encoding, or the h2 END_STREAM flag). An unconditionally attached empty stream made bodyless POSTs — zero-argument scripted server function calls, synthetic dispatches — parse as a present-but-unusable body, which @solidjs/web 2.0.0-rc.5 rejects as malformed (400) instead of ignoring.
5 changes: 5 additions & 0 deletions .changeset/dev-middleware-data-address.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solidjs/vite-plugin': patch
---

Dev middleware recognizes the scripted transport's data address. Scripted server-function calls now go to `<endpoint>/data/<id>` (solidjs/solid#3094), and the middleware's module-preload step assumed exactly one path segment after the mount — a cold function only client code references would never be evaluated in the SSR environment for a data-addressed call, answering 404 under `vite dev`. Dispatch itself was unaffected (mount matching is prefix-based). The id now parses from behind the literal `data` segment too; a function id spelled `data` still parses at the bare address, since an id occupies exactly one segment.
5 changes: 5 additions & 0 deletions .changeset/drop-legacy-server-function-addressing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/vite-plugin": patch
---

Drop the retired `X-Server-Function-Id` header and `?id=` addressing fallback from the dev middleware's module-preload path. Addressing is path-only (`<endpoint>/<id>` and `<endpoint>/data/<id>`), matching the runtime's removal of its own transitional shims during the RC.
5 changes: 5 additions & 0 deletions .changeset/id-hash-second-segment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/vite-plugin": patch
---

Read the file hash from the second id segment. Server-function ids are now identity-keyed `<name>-<hash>[-<ordinal>]` (solidjs/solid#3109) instead of positional `<hash>-<ordinal>`, so the dev middleware's id-to-module lookup takes the hash from `split('-')[1]` rather than the first segment.
10 changes: 9 additions & 1 deletion examples/start-ssr/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ const MIME = {
function webRequest(req) {
const url = new URL(req.url || '/', `http://${req.headers.host || `localhost:${port}`}`);
const method = req.method || 'GET';
const body = method === 'GET' || method === 'HEAD' ? undefined : Readable.toWeb(req);
// Attach a body only when the request carries one (Content-Length or
// Transfer-Encoding, RFC 9112 §6): the runtime treats a present body that
// decodes to nothing as malformed since @solidjs/web 2.0.0-rc.5.
const hasBody =
method !== 'GET' &&
method !== 'HEAD' &&
(req.headers['transfer-encoding'] !== undefined ||
(req.headers['content-length'] !== undefined && req.headers['content-length'] !== '0'));
const body = hasBody ? Readable.toWeb(req) : undefined;
return new Request(url, {
method,
headers: req.headers,
Expand Down
4 changes: 2 additions & 2 deletions examples/start-ssr/test/host-dispatch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ try {
// manifest (the same signal the browser's module request sends in a real
// session); it also yields the compiled reference to pull the id from.
const transformed = await server.transformRequest('/src/api.ts');
const match = /createServerReference\w*\("([^"]*-getServerMessage)"/.exec(transformed?.code || '');
const match = /createServerReference\w*\("(getServerMessage-[^"]*)"/.exec(transformed?.code || '');
if (!match) throw new Error('could not extract function id from transformed module');

const runner = server.environments.ssr.runner;
Expand All @@ -40,7 +40,7 @@ try {
const body = await response.text();
console.log(`HOST-DISPATCH ${response.status} ${body}`);

const nativeMatch = /createServerReference\w*\("([^"]*-nativeAddress)"/.exec(
const nativeMatch = /createServerReference\w*\("(nativeAddress-[^"]*)"/.exec(
transformed?.code || '',
);
if (!nativeMatch) throw new Error('could not extract nativeAddress function id');
Expand Down
46 changes: 28 additions & 18 deletions examples/start-ssr/test/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -297,25 +297,34 @@ function record(mode, phase, name, ok, detail = '') {
console.log(` [${mode}/${phase}] ${status} ${name}${detail && !ok ? ` — ${detail}` : ''}`);
}

// Dev function IDs are `hash-count-name`; pull the one for `name` out of the
// client-transformed module so the endpoint can be hit directly.
// Pull the function id for `name` out of the client-transformed module so
// the endpoint can be hit directly.
function extractFunctionId(transformedCode, name) {
// The import identifier may be aliased (e.g. createServerReference_1), and
// newer compilers pass the function name as a second argument after the id.
const match = transformedCode.match(new RegExp(`createServerReference\\w*\\("([^"]*-${name})"`));
// The import identifier may be aliased (e.g. createServerReference_1).
// Ids are identity-keyed `<name>-<hash>[-<ordinal>]` (solidjs/solid#3109);
// the literal `-` after the name keeps e.g. `getServerMessage2` from
// matching a probe for `getServerMessage`.
const match = transformedCode.match(new RegExp(`createServerReference\\w*\\("(${name}-[^"]*)"`));
return match ? match[1] : null;
}

async function runCsrfChecks(mode, origin) {
const crossSite = await fetch(origin + '/_server/csrf-probe', {
method: 'POST',
headers: { 'Sec-Fetch-Site': 'cross-site' },
});
async function runCsrfChecks(mode, origin, registeredId) {
// The runtime answers unknown ids 404 before the same-origin check runs
// (@solidjs/web 2.0.0-rc.5), so the rejection must be probed against a
// registered function id.
const crossSite = await fetch(
`${origin}/_server/${encodeURIComponent(registeredId || 'csrf-probe')}`,
{
method: 'POST',
headers: { 'Sec-Fetch-Site': 'cross-site' },
},
);
record(
mode,
'csrf',
'cross-site server function request rejected',
crossSite.status === 403,
registeredId ? `status ${crossSite.status}` : 'no registered id to probe',
);

const sameOrigin = await fetch(origin + '/_server/csrf-probe', { method: 'POST' });
Expand Down Expand Up @@ -924,7 +933,7 @@ async function runDevMode() {
);
const bogus = await fetch(origin + '/_server/bogus-0');
record(mode, 'sf', 'dev middleware rejects unknown id', bogus.status === 404);
await runCsrfChecks(mode, origin);
await runCsrfChecks(mode, origin, functionId);

const html = await runSsrChecks(mode, origin);
record(mode, 'dev', 'Vite client injected into <head>', html.includes('/@vite/client'));
Expand Down Expand Up @@ -1192,7 +1201,8 @@ async function runProdMode() {

const bogus = await fetch(origin + '/_server/bogus-0');
record(mode, 'sf', 'prod handler rejects unknown id', bogus.status === 404);
await runCsrfChecks(mode, origin);
const registeredId = serverBundle.match(/registerServerReference\w*\("([^"]+)"/)?.[1] ?? null;
await runCsrfChecks(mode, origin, registeredId);

const html = await runSsrChecks(mode, origin);
record(
Expand Down Expand Up @@ -1771,10 +1781,10 @@ async function runConfigureMode() {
});
captureLog(server);
await waitForHttp(origin + '/', 30000, { headers: { accept: 'text/html' } });
// Production ids are the dev id minus its dev-only trailing `-name`
// segment (`hash-count` vs `hash-count-name`), so the dev phase's id
// carries over. Dispatch before any page render, like dev.
const prodId = functionId ? functionId.replace(/-configureProbe$/, '') : null;
// Identity-keyed ids (`<name>-<hash>[-<ordinal>]`, solidjs/solid#3109)
// are the same in dev and prod, so the dev phase's id carries over
// as-is. Dispatch before any page render, like dev.
const prodId = functionId;
const prod = prodId ? await dispatch(prodId) : null;
record(
mode,
Expand Down Expand Up @@ -2837,8 +2847,8 @@ async function runMiddlewareMode() {
});
captureLog(server);
await waitForHttp(prodOrigin + '/', 30000, { headers: { accept: 'text/html' } });
// Prod ids drop the dev-only trailing `-name` segment.
const prodId = functionId ? functionId.replace(/-whoAmI$/, '') : null;
// Identity-keyed ids are the same in dev and prod (solidjs/solid#3109).
const prodId = functionId;
await runMiddlewareChecksOverHttp('mw-prod', prodOrigin, prodId);
await runHttpChecks('mw-prod', prodOrigin);
} catch (e) {
Expand Down
Loading
Loading