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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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