spark-ssr 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/server.js +67 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spark-ssr",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Zero-config SSR for spark-html on Bun. The HTML template infers everything: filesystem routing, layouts, <spark-ssr> declarative data (SQL, URLs, globs, modules), auto CRUD with validation, guards, no-JS forms, schema + seeds, live updates, SEO. No build step.",
|
|
5
5
|
"homepage": "https://wilkinnovo.github.io/spark-html",
|
|
6
6
|
"type": "module",
|
package/src/server.js
CHANGED
|
@@ -172,11 +172,18 @@ function pageData(page, cache, pagesDir) {
|
|
|
172
172
|
const pageP = parsed[parsed.length - 1];
|
|
173
173
|
|
|
174
174
|
// Compose bodies innermost-out: the page replaces each layout's <slot>.
|
|
175
|
+
// Comments are masked first so a literal <slot> written inside a layout
|
|
176
|
+
// comment (the template's own explainer text does this) isn't mistaken for
|
|
177
|
+
// the real slot — which would inject the whole page inside the comment.
|
|
175
178
|
let body = pageP.body;
|
|
176
179
|
for (let i = parsed.length - 2; i >= 0; i--) {
|
|
177
180
|
const lay = parsed[i].body;
|
|
178
181
|
const SLOT = /<slot\b[^>]*>(?:\s*<\/slot>)?/i;
|
|
179
|
-
|
|
182
|
+
const { masked, restore } = maskComments(lay);
|
|
183
|
+
const m = masked.match(SLOT);
|
|
184
|
+
body = m
|
|
185
|
+
? restore(masked.slice(0, m.index)) + body + restore(masked.slice(m.index + m[0].length))
|
|
186
|
+
: lay + body;
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
const blocks = parsed.flatMap((p) => p.blocks);
|
|
@@ -1098,15 +1105,67 @@ export async function serve(options = {}) {
|
|
|
1098
1105
|
});
|
|
1099
1106
|
}
|
|
1100
1107
|
|
|
1101
|
-
|
|
1108
|
+
// Human copy for the built-in default error screen (used when the app ships
|
|
1109
|
+
// no <status>.html of its own).
|
|
1110
|
+
const STATUS_INFO = {
|
|
1111
|
+
400: ['Bad request', 'That request could not be understood.'],
|
|
1112
|
+
401: ['Sign in required', 'You need to sign in to view this page.'],
|
|
1113
|
+
403: ['Forbidden', "You don't have access to this page."],
|
|
1114
|
+
404: ['Page not found', "The page you're looking for doesn't exist — it may have moved."],
|
|
1115
|
+
500: ['Server error', 'Something went wrong on our end. Try again in a moment.'],
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// Zero-config error screen: a styled, self-contained page in the Spark design
|
|
1119
|
+
// system (dark default, gold ⚡, monospace) — no dependency on the app's
|
|
1120
|
+
// layout or data, so it renders even when those are what failed. Apps override
|
|
1121
|
+
// it by dropping a <status>.html in pages/ (or the project root).
|
|
1122
|
+
function defaultErrorPage(status) {
|
|
1123
|
+
const [title, blurb] = STATUS_INFO[status] || ['Error', 'Something went wrong.'];
|
|
1124
|
+
const body = `<!doctype html>
|
|
1125
|
+
<html lang="en"><head><meta charset="utf-8">
|
|
1126
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1127
|
+
<meta name="robots" content="noindex">
|
|
1128
|
+
<title>${status} · ${escapeHtml(title)}</title>
|
|
1129
|
+
<style>
|
|
1130
|
+
:root{color-scheme:dark light}
|
|
1131
|
+
*{box-sizing:border-box}
|
|
1132
|
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
|
|
1133
|
+
background:#000;color:#fff;text-align:center;padding:2rem;
|
|
1134
|
+
font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.6}
|
|
1135
|
+
@media(prefers-color-scheme:light){body{background:#fff;color:#1a1a1a}}
|
|
1136
|
+
.bolt{font-size:2.25rem;filter:drop-shadow(0 0 16px rgba(255,210,74,.45))}
|
|
1137
|
+
.code{font-size:clamp(3.5rem,14vw,6rem);font-weight:800;letter-spacing:-.04em;margin:.25rem 0 0;
|
|
1138
|
+
background:linear-gradient(110deg,currentColor,#ffd24a);-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
1139
|
+
h1{font-size:1.15rem;font-weight:700;margin:.25rem 0 .5rem}
|
|
1140
|
+
p{color:#888;max-width:32rem;margin:0 auto 1.5rem;font-size:.95rem}
|
|
1141
|
+
@media(prefers-color-scheme:light){p{color:#666}}
|
|
1142
|
+
a{display:inline-block;color:#000;background:#ffd24a;text-decoration:none;font-weight:700;
|
|
1143
|
+
padding:.6rem 1.2rem;border-radius:8px;font-size:.9rem}
|
|
1144
|
+
a:active{transform:scale(.97)}
|
|
1145
|
+
</style></head>
|
|
1146
|
+
<body><main>
|
|
1147
|
+
<div class="bolt">⚡</div>
|
|
1148
|
+
<div class="code">${status}</div>
|
|
1149
|
+
<h1>${escapeHtml(title)}</h1>
|
|
1150
|
+
<p>${escapeHtml(blurb)}</p>
|
|
1151
|
+
<a href="/">← Back home</a>
|
|
1152
|
+
</main></body></html>`;
|
|
1153
|
+
return body;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1102
1156
|
function errorPage(status) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1157
|
+
// Override precedence: pages/<status>.html (filesystem convention) →
|
|
1158
|
+
// <root>/<status>.html (back-compat) → the built-in default.
|
|
1159
|
+
for (const dir of new Set([pagesDir, root])) {
|
|
1160
|
+
const file = join(dir, `${status}.html`);
|
|
1161
|
+
if (existsSync(file)) {
|
|
1162
|
+
// The reload client rides along so fixing the page un-sticks the browser.
|
|
1163
|
+
const custom = readFileSync(file, 'utf8') + (live ? '\n' + RELOAD_CLIENT : '');
|
|
1164
|
+
return new Response(custom, { status, headers: { 'content-type': 'text/html; charset=utf-8' } });
|
|
1165
|
+
}
|
|
1108
1166
|
}
|
|
1109
|
-
|
|
1167
|
+
const body = defaultErrorPage(status) + (live ? '\n' + RELOAD_CLIENT : '');
|
|
1168
|
+
return new Response(body, { status, headers: { 'content-type': 'text/html; charset=utf-8' } });
|
|
1110
1169
|
}
|
|
1111
1170
|
|
|
1112
1171
|
// Dev-only error overlay (§4): the real error — SQL, file, line — on the
|