sibujs 1.2.0 → 1.4.0
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/README.md +29 -25
- package/dist/browser.cjs +804 -2
- package/dist/browser.d.cts +591 -1
- package/dist/browser.d.ts +591 -1
- package/dist/browser.js +50 -8
- package/dist/build.cjs +655 -237
- package/dist/build.js +15 -93
- package/dist/cdn.global.js +188 -7
- package/dist/chunk-2BYQDGN3.js +742 -0
- package/dist/chunk-32DY64NT.js +282 -0
- package/dist/chunk-3AIRKM3B.js +1263 -0
- package/dist/chunk-3X2YG6YM.js +505 -0
- package/dist/chunk-5X6PP2UK.js +28 -0
- package/dist/chunk-77L6NL3X.js +1097 -0
- package/dist/chunk-BGN5ZMP4.js +26 -0
- package/dist/chunk-BTU3TJDS.js +365 -0
- package/dist/chunk-CHF5OHIA.js +61 -0
- package/dist/chunk-CMBFNA7L.js +27 -0
- package/dist/chunk-CNZ35WI2.js +178 -0
- package/dist/chunk-DAHRH4ON.js +331 -0
- package/dist/chunk-EBGIRKQY.js +616 -0
- package/dist/chunk-EUZND3CB.js +27 -0
- package/dist/chunk-F3FA4F32.js +292 -0
- package/dist/chunk-JAKHTMQU.js +1000 -0
- package/dist/chunk-JCI5M6U6.js +956 -0
- package/dist/chunk-KQPDEVVS.js +398 -0
- package/dist/chunk-M4NLBH4I.js +725 -0
- package/dist/chunk-NEKUBFPT.js +60 -0
- package/dist/chunk-NYVAC6P5.js +37 -0
- package/dist/chunk-PTQJDMRT.js +146 -0
- package/dist/chunk-QWZG56ET.js +2744 -0
- package/dist/chunk-TSOKIX5Z.js +654 -0
- package/dist/chunk-UHNL42EF.js +2730 -0
- package/dist/chunk-VRW3FULF.js +725 -0
- package/dist/chunk-WZSPOOER.js +84 -0
- package/dist/chunk-YT6HQ6AM.js +14 -0
- package/dist/chunk-ZD6OAMTH.js +277 -0
- package/dist/chunk-ZWKZCBO6.js +317 -0
- package/dist/contracts-DDrwxvJ-.d.cts +245 -0
- package/dist/contracts-DDrwxvJ-.d.ts +245 -0
- package/dist/contracts-xo5ckdRP.d.cts +240 -0
- package/dist/contracts-xo5ckdRP.d.ts +240 -0
- package/dist/data.cjs +35 -2
- package/dist/data.d.cts +7 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.js +9 -8
- package/dist/devtools.cjs +122 -0
- package/dist/devtools.d.cts +69 -461
- package/dist/devtools.d.ts +69 -461
- package/dist/devtools.js +127 -6
- package/dist/ecosystem.cjs +23 -6
- package/dist/ecosystem.d.cts +1 -1
- package/dist/ecosystem.d.ts +1 -1
- package/dist/ecosystem.js +10 -9
- package/dist/extras.cjs +1208 -88
- package/dist/extras.d.cts +6 -6
- package/dist/extras.d.ts +6 -6
- package/dist/extras.js +70 -33
- package/dist/index.cjs +663 -158
- package/dist/index.d.cts +398 -40
- package/dist/index.d.ts +398 -40
- package/dist/index.js +39 -21
- package/dist/introspect-BumjnBKr.d.cts +477 -0
- package/dist/introspect-CZrlcaYy.d.ts +477 -0
- package/dist/introspect-Cb0zgpi2.d.cts +477 -0
- package/dist/introspect-Y2xNXGSf.d.ts +477 -0
- package/dist/motion.js +4 -4
- package/dist/patterns.cjs +51 -24
- package/dist/patterns.d.cts +19 -57
- package/dist/patterns.d.ts +19 -57
- package/dist/patterns.js +8 -16
- package/dist/performance.js +4 -4
- package/dist/plugins.cjs +429 -82
- package/dist/plugins.d.cts +27 -4
- package/dist/plugins.d.ts +27 -4
- package/dist/plugins.js +156 -37
- package/dist/ssr-4PBXAOO3.js +40 -0
- package/dist/ssr-Do_SiVoL.d.cts +201 -0
- package/dist/ssr-Do_SiVoL.d.ts +201 -0
- package/dist/ssr.cjs +312 -60
- package/dist/ssr.d.cts +10 -1
- package/dist/ssr.d.ts +10 -1
- package/dist/ssr.js +13 -10
- package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
- package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
- package/dist/testing.cjs +233 -2
- package/dist/testing.d.cts +42 -1
- package/dist/testing.d.ts +42 -1
- package/dist/testing.js +129 -2
- package/dist/ui.cjs +374 -8
- package/dist/ui.d.cts +252 -2
- package/dist/ui.d.ts +252 -2
- package/dist/ui.js +329 -11
- package/dist/widgets.js +7 -7
- package/package.json +1 -1
package/dist/ssr.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(ssr_exports, {
|
|
|
33
33
|
createWorkerPool: () => createWorkerPool,
|
|
34
34
|
defineRemoteComponent: () => defineRemoteComponent,
|
|
35
35
|
deserializeState: () => deserializeState,
|
|
36
|
+
escapeScriptJson: () => escapeScriptJson,
|
|
36
37
|
generateStaticSite: () => generateStaticSite,
|
|
37
38
|
hydrate: () => hydrate,
|
|
38
39
|
hydrateIslands: () => hydrateIslands,
|
|
@@ -78,14 +79,63 @@ function devWarn(message) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// src/utils/sanitize.ts
|
|
83
|
+
function sanitizeUrl(url) {
|
|
84
|
+
const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
|
|
85
|
+
if (!trimmed) return "";
|
|
86
|
+
const lower = trimmed.toLowerCase();
|
|
87
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
return trimmed;
|
|
91
|
+
}
|
|
92
|
+
function sanitizeCSSValue(value) {
|
|
93
|
+
const lower = value.toLowerCase().replace(/\s+/g, "");
|
|
94
|
+
if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
|
|
100
|
+
function isUrlAttribute(attr) {
|
|
101
|
+
return URL_ATTRIBUTES.has(attr);
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
// src/platform/ssr.ts
|
|
82
105
|
var _isDev2 = isDev();
|
|
106
|
+
var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
107
|
+
function isSafeAttrName(name) {
|
|
108
|
+
return SAFE_ATTR_NAME.test(name);
|
|
109
|
+
}
|
|
110
|
+
function isEventHandlerAttr(name) {
|
|
111
|
+
if (name.length < 3) return false;
|
|
112
|
+
const lower = name.toLowerCase();
|
|
113
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
114
|
+
}
|
|
115
|
+
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
116
|
+
"href",
|
|
117
|
+
"src",
|
|
118
|
+
"action",
|
|
119
|
+
"formaction",
|
|
120
|
+
"cite",
|
|
121
|
+
"poster",
|
|
122
|
+
"background",
|
|
123
|
+
"srcset",
|
|
124
|
+
"ping",
|
|
125
|
+
"manifest",
|
|
126
|
+
"data",
|
|
127
|
+
"xlink:href"
|
|
128
|
+
]);
|
|
83
129
|
function ssrErrorComment(err) {
|
|
84
130
|
if (_isDev2) {
|
|
85
|
-
|
|
131
|
+
const msg = escapeHtml(err instanceof Error ? err.message : String(err));
|
|
132
|
+
return `<!--SSR error: ${safeCommentText(msg)}-->`;
|
|
86
133
|
}
|
|
87
134
|
return "<!--SSR error-->";
|
|
88
135
|
}
|
|
136
|
+
function safeCommentText(text2) {
|
|
137
|
+
return text2.replace(/-->/g, "-->").replace(/--!>/g, "--!>").replace(/<!--/g, "<!--").replace(/--$/g, "---");
|
|
138
|
+
}
|
|
89
139
|
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
90
140
|
"area",
|
|
91
141
|
"base",
|
|
@@ -116,16 +166,30 @@ function renderToString(element) {
|
|
|
116
166
|
return escapeHtml(element.textContent || "");
|
|
117
167
|
}
|
|
118
168
|
if (element.nodeType === 8) {
|
|
119
|
-
|
|
120
|
-
return `<!--${content}-->`;
|
|
169
|
+
return `<!--${safeCommentText(element.textContent || "")}-->`;
|
|
121
170
|
}
|
|
122
171
|
if (!(element instanceof HTMLElement)) {
|
|
123
|
-
return element.textContent || "";
|
|
172
|
+
return escapeHtml(element.textContent || "");
|
|
124
173
|
}
|
|
125
174
|
const tag = element.tagName.toLowerCase();
|
|
175
|
+
if (tag === "script" || tag === "style") {
|
|
176
|
+
return _isDev2 ? `<!--ssr:${tag}-stripped-->` : "";
|
|
177
|
+
}
|
|
178
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
|
|
179
|
+
return _isDev2 ? "<!--ssr:invalid-tag-->" : "";
|
|
180
|
+
}
|
|
126
181
|
let html2 = `<${tag}`;
|
|
127
182
|
for (const attr of Array.from(element.attributes)) {
|
|
128
|
-
|
|
183
|
+
const rawName = attr.name;
|
|
184
|
+
if (!isSafeAttrName(rawName)) continue;
|
|
185
|
+
if (isEventHandlerAttr(rawName)) continue;
|
|
186
|
+
const lowerName = rawName.toLowerCase();
|
|
187
|
+
let value = attr.value;
|
|
188
|
+
if (URL_ATTRS.has(lowerName)) {
|
|
189
|
+
value = sanitizeUrl(value);
|
|
190
|
+
if (!value) continue;
|
|
191
|
+
}
|
|
192
|
+
html2 += ` ${rawName}="${escapeAttr(value)}"`;
|
|
129
193
|
}
|
|
130
194
|
if (element.dataset && !element.dataset.sibuHydrate) {
|
|
131
195
|
html2 += ` data-sibu-ssr="true"`;
|
|
@@ -144,8 +208,25 @@ function renderToString(element) {
|
|
|
144
208
|
html2 += `</${tag}>`;
|
|
145
209
|
return html2;
|
|
146
210
|
}
|
|
147
|
-
function hydrate(component, container) {
|
|
211
|
+
function hydrate(component, container, options = {}) {
|
|
148
212
|
const clientTree = component();
|
|
213
|
+
if (options.diagnostics) {
|
|
214
|
+
const mismatches = [];
|
|
215
|
+
collectMismatches(container.firstElementChild, clientTree, "", mismatches);
|
|
216
|
+
if (mismatches.length > 0) {
|
|
217
|
+
const first = mismatches[0];
|
|
218
|
+
if (options.onMismatch) {
|
|
219
|
+
options.onMismatch(first);
|
|
220
|
+
} else if (_isDev2) {
|
|
221
|
+
console.warn(
|
|
222
|
+
`[Sibu hydration] ${first.message}
|
|
223
|
+
at ${first.path}
|
|
224
|
+
server: ${first.serverValue}
|
|
225
|
+
client: ${first.clientValue}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
149
230
|
hydrateNode(container.firstElementChild, clientTree);
|
|
150
231
|
container.setAttribute("data-sibu-hydrated", "true");
|
|
151
232
|
}
|
|
@@ -157,9 +238,119 @@ function hydrateNode(serverNode, clientNode) {
|
|
|
157
238
|
hydrateNode(serverChildren[i2], clientChildren[i2]);
|
|
158
239
|
}
|
|
159
240
|
}
|
|
241
|
+
function collectMismatches(serverNode, clientNode, path2, out, max = 5) {
|
|
242
|
+
if (out.length >= max) return;
|
|
243
|
+
const nodePath = path2 || clientNode?.tagName?.toLowerCase() || "(root)";
|
|
244
|
+
if (!serverNode && clientNode) {
|
|
245
|
+
out.push({
|
|
246
|
+
kind: "child-count",
|
|
247
|
+
path: nodePath,
|
|
248
|
+
serverValue: "(missing)",
|
|
249
|
+
clientValue: clientNode.tagName.toLowerCase(),
|
|
250
|
+
message: "Client rendered a node that the server did not emit."
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (serverNode && !clientNode) {
|
|
255
|
+
out.push({
|
|
256
|
+
kind: "child-count",
|
|
257
|
+
path: nodePath,
|
|
258
|
+
serverValue: serverNode.tagName.toLowerCase(),
|
|
259
|
+
clientValue: "(missing)",
|
|
260
|
+
message: "Server rendered a node that the client did not produce."
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!serverNode || !clientNode) return;
|
|
265
|
+
if (serverNode.tagName !== clientNode.tagName) {
|
|
266
|
+
out.push({
|
|
267
|
+
kind: "tag",
|
|
268
|
+
path: nodePath,
|
|
269
|
+
serverValue: serverNode.tagName.toLowerCase(),
|
|
270
|
+
clientValue: clientNode.tagName.toLowerCase(),
|
|
271
|
+
message: "Element tag mismatch \u2014 server and client disagree on the element type."
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const skipAttrs = /* @__PURE__ */ new Set(["data-sibu-ssr", "data-sibu-hydrated", "data-sibu-island"]);
|
|
276
|
+
const serverAttrs = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const a2 of Array.from(serverNode.attributes)) {
|
|
278
|
+
if (!skipAttrs.has(a2.name)) serverAttrs.set(a2.name, a2.value);
|
|
279
|
+
}
|
|
280
|
+
const clientAttrs = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const a2 of Array.from(clientNode.attributes)) {
|
|
282
|
+
if (!skipAttrs.has(a2.name)) clientAttrs.set(a2.name, a2.value);
|
|
283
|
+
}
|
|
284
|
+
for (const [name, value] of serverAttrs) {
|
|
285
|
+
if (out.length >= max) return;
|
|
286
|
+
if (!clientAttrs.has(name)) {
|
|
287
|
+
out.push({
|
|
288
|
+
kind: "attribute",
|
|
289
|
+
path: `${nodePath}[${name}]`,
|
|
290
|
+
serverValue: value,
|
|
291
|
+
clientValue: "(missing)",
|
|
292
|
+
message: `Attribute "${name}" present on server but missing on client.`
|
|
293
|
+
});
|
|
294
|
+
} else if (clientAttrs.get(name) !== value) {
|
|
295
|
+
out.push({
|
|
296
|
+
kind: "attribute",
|
|
297
|
+
path: `${nodePath}[${name}]`,
|
|
298
|
+
serverValue: value,
|
|
299
|
+
clientValue: clientAttrs.get(name) ?? "",
|
|
300
|
+
message: `Attribute "${name}" differs between server and client.`
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const [name, value] of clientAttrs) {
|
|
305
|
+
if (out.length >= max) return;
|
|
306
|
+
if (!serverAttrs.has(name)) {
|
|
307
|
+
out.push({
|
|
308
|
+
kind: "attribute",
|
|
309
|
+
path: `${nodePath}[${name}]`,
|
|
310
|
+
serverValue: "(missing)",
|
|
311
|
+
clientValue: value,
|
|
312
|
+
message: `Attribute "${name}" present on client but missing on server.`
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const serverChildren = Array.from(serverNode.children);
|
|
317
|
+
const clientChildren = Array.from(clientNode.children);
|
|
318
|
+
const max2 = Math.max(serverChildren.length, clientChildren.length);
|
|
319
|
+
for (let i2 = 0; i2 < max2; i2++) {
|
|
320
|
+
if (out.length >= max) return;
|
|
321
|
+
const childPath = `${nodePath} > ${clientChildren[i2]?.tagName?.toLowerCase() ?? serverChildren[i2]?.tagName?.toLowerCase() ?? "?"}:nth-child(${i2 + 1})`;
|
|
322
|
+
collectMismatches(serverChildren[i2] ?? null, clientChildren[i2] ?? null, childPath, out, max);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
160
325
|
function trustHTML(html2) {
|
|
161
326
|
return html2;
|
|
162
327
|
}
|
|
328
|
+
function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
329
|
+
if (!attrs) return "";
|
|
330
|
+
const out = [];
|
|
331
|
+
for (const rawKey of Object.keys(attrs)) {
|
|
332
|
+
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
333
|
+
if (!isSafeAttrName(rawKey)) continue;
|
|
334
|
+
if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
|
|
335
|
+
const lowerKey = rawKey.toLowerCase();
|
|
336
|
+
let value = String(attrs[rawKey]);
|
|
337
|
+
if (URL_ATTRS.has(lowerKey)) {
|
|
338
|
+
value = sanitizeUrl(value);
|
|
339
|
+
if (!value) continue;
|
|
340
|
+
}
|
|
341
|
+
out.push(`${rawKey}="${escapeAttr(value)}"`);
|
|
342
|
+
}
|
|
343
|
+
return out.join(" ");
|
|
344
|
+
}
|
|
345
|
+
function isDangerousMetaRefresh(metaProps) {
|
|
346
|
+
const httpEquiv = metaProps["http-equiv"];
|
|
347
|
+
if (typeof httpEquiv !== "string") return false;
|
|
348
|
+
if (httpEquiv.toLowerCase() !== "refresh") return false;
|
|
349
|
+
const content = metaProps.content;
|
|
350
|
+
if (typeof content !== "string") return false;
|
|
351
|
+
const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
|
|
352
|
+
return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
|
|
353
|
+
}
|
|
163
354
|
function renderToDocument(component, options = {}) {
|
|
164
355
|
let content;
|
|
165
356
|
try {
|
|
@@ -167,14 +358,22 @@ function renderToDocument(component, options = {}) {
|
|
|
167
358
|
} catch (err) {
|
|
168
359
|
content = ssrErrorComment(err);
|
|
169
360
|
}
|
|
170
|
-
const metaTags = (options.meta || []).map(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
).
|
|
176
|
-
|
|
177
|
-
|
|
361
|
+
const metaTags = (options.meta || []).map((attrs) => {
|
|
362
|
+
if (isDangerousMetaRefresh(attrs)) return "";
|
|
363
|
+
const pairs = buildAttrString(attrs);
|
|
364
|
+
return pairs ? `<meta ${pairs} />` : "";
|
|
365
|
+
}).filter(Boolean).join("\n ");
|
|
366
|
+
const linkTags = (options.links || []).map((attrs) => {
|
|
367
|
+
const pairs = buildAttrString(attrs);
|
|
368
|
+
return pairs ? `<link ${pairs} />` : "";
|
|
369
|
+
}).filter(Boolean).join("\n ");
|
|
370
|
+
const scriptTags = (options.scripts || []).map((src) => {
|
|
371
|
+
const safe = sanitizeUrl(String(src));
|
|
372
|
+
if (!safe) return "";
|
|
373
|
+
return `<script src="${escapeAttr(safe)}"></script>`;
|
|
374
|
+
}).filter(Boolean).join("\n ");
|
|
375
|
+
const bodyAttrPairs = buildAttrString(options.bodyAttrs);
|
|
376
|
+
const bodyAttrs = bodyAttrPairs ? ` ${bodyAttrPairs}` : "";
|
|
178
377
|
return `<!DOCTYPE html>
|
|
179
378
|
<html>
|
|
180
379
|
<head>
|
|
@@ -207,18 +406,34 @@ async function* renderToStream(element) {
|
|
|
207
406
|
return;
|
|
208
407
|
}
|
|
209
408
|
if (element.nodeType === 8) {
|
|
210
|
-
|
|
211
|
-
yield `<!--${content}-->`;
|
|
409
|
+
yield `<!--${safeCommentText(element.textContent || "")}-->`;
|
|
212
410
|
return;
|
|
213
411
|
}
|
|
214
412
|
if (!(element instanceof HTMLElement)) {
|
|
215
|
-
yield element.textContent || "";
|
|
413
|
+
yield escapeHtml(element.textContent || "");
|
|
216
414
|
return;
|
|
217
415
|
}
|
|
218
416
|
const tag = element.tagName.toLowerCase();
|
|
417
|
+
if (tag === "script" || tag === "style") {
|
|
418
|
+
if (_isDev2) yield `<!--ssr:${tag}-stripped-->`;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
|
|
422
|
+
if (_isDev2) yield "<!--ssr:invalid-tag-->";
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
219
425
|
let openTag = `<${tag}`;
|
|
220
426
|
for (const attr of Array.from(element.attributes)) {
|
|
221
|
-
|
|
427
|
+
const rawName = attr.name;
|
|
428
|
+
if (!isSafeAttrName(rawName)) continue;
|
|
429
|
+
if (isEventHandlerAttr(rawName)) continue;
|
|
430
|
+
const lowerName = rawName.toLowerCase();
|
|
431
|
+
let value = attr.value;
|
|
432
|
+
if (URL_ATTRS.has(lowerName)) {
|
|
433
|
+
value = sanitizeUrl(value);
|
|
434
|
+
if (!value) continue;
|
|
435
|
+
}
|
|
436
|
+
openTag += ` ${rawName}="${escapeAttr(value)}"`;
|
|
222
437
|
}
|
|
223
438
|
if (VOID_ELEMENTS.has(tag)) {
|
|
224
439
|
yield `${openTag} />`;
|
|
@@ -266,8 +481,9 @@ function hydrateIslands(container, islands) {
|
|
|
266
481
|
const markers = container.querySelectorAll("[data-sibu-island]");
|
|
267
482
|
for (const marker2 of Array.from(markers)) {
|
|
268
483
|
const id = marker2.getAttribute("data-sibu-island") ?? "";
|
|
484
|
+
if (!Object.hasOwn(islands, id)) continue;
|
|
269
485
|
const factory = islands[id];
|
|
270
|
-
if (
|
|
486
|
+
if (typeof factory !== "function") continue;
|
|
271
487
|
const clientTree = factory();
|
|
272
488
|
hydrateNode(marker2, clientTree);
|
|
273
489
|
marker2.setAttribute("data-sibu-hydrated", "true");
|
|
@@ -279,8 +495,9 @@ function hydrateProgressively(container, islands, options) {
|
|
|
279
495
|
const cleanups = [];
|
|
280
496
|
for (const marker2 of Array.from(markers)) {
|
|
281
497
|
const id = marker2.getAttribute("data-sibu-island") ?? "";
|
|
498
|
+
if (!Object.hasOwn(islands, id)) continue;
|
|
282
499
|
const factory = islands[id];
|
|
283
|
-
if (
|
|
500
|
+
if (typeof factory !== "function") continue;
|
|
284
501
|
const observer = new IntersectionObserver(
|
|
285
502
|
(entries) => {
|
|
286
503
|
for (const entry of entries) {
|
|
@@ -319,24 +536,33 @@ function ssrSuspense(props) {
|
|
|
319
536
|
}));
|
|
320
537
|
return { element: wrapper, promise };
|
|
321
538
|
}
|
|
539
|
+
var SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
|
|
322
540
|
function suspenseSwapScript(id, nonce) {
|
|
323
|
-
|
|
541
|
+
if (!SAFE_SUSPENSE_ID.test(id)) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`[SibuJS SSR] suspenseSwapScript: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
324
546
|
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
325
|
-
return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${
|
|
547
|
+
return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${id}");var f=document.querySelector('[data-sibu-suspense-id="${id}"]');if(t&&f){while(t.firstChild)f.appendChild(t.firstChild);t.remove();f.removeAttribute("data-sibu-suspense-id");}})()</script>`;
|
|
326
548
|
}
|
|
327
|
-
async function* renderToSuspenseStream(element, pendingBoundaries = []) {
|
|
549
|
+
async function* renderToSuspenseStream(element, pendingBoundaries = [], options) {
|
|
328
550
|
yield* renderToStream(element);
|
|
329
551
|
if (pendingBoundaries.length > 0) {
|
|
330
552
|
const resolved = await Promise.all(pendingBoundaries);
|
|
331
553
|
for (const { id, html: html2 } of resolved) {
|
|
332
|
-
|
|
333
|
-
yield
|
|
554
|
+
if (!SAFE_SUSPENSE_ID.test(id)) continue;
|
|
555
|
+
yield `<div hidden id="sibu-resolved-${id}">${html2}</div>`;
|
|
556
|
+
yield suspenseSwapScript(id, options?.nonce);
|
|
334
557
|
}
|
|
335
558
|
}
|
|
336
559
|
}
|
|
337
560
|
var SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
|
|
561
|
+
function escapeScriptJson(json) {
|
|
562
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
563
|
+
}
|
|
338
564
|
function serializeState(state, nonce) {
|
|
339
|
-
const json = JSON.stringify(state)
|
|
565
|
+
const json = escapeScriptJson(JSON.stringify(state));
|
|
340
566
|
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
341
567
|
return `<script${nonceAttr}>window.${SSR_DATA_ATTR}=${json}</script>`;
|
|
342
568
|
}
|
|
@@ -351,7 +577,7 @@ function escapeHtml(str) {
|
|
|
351
577
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
352
578
|
}
|
|
353
579
|
function escapeAttr(str) {
|
|
354
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
580
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
355
581
|
}
|
|
356
582
|
|
|
357
583
|
// src/reactivity/track.ts
|
|
@@ -622,34 +848,35 @@ function effect(effectFn, options) {
|
|
|
622
848
|
};
|
|
623
849
|
}
|
|
624
850
|
|
|
625
|
-
// src/utils/sanitize.ts
|
|
626
|
-
function sanitizeUrl(url) {
|
|
627
|
-
const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
|
|
628
|
-
if (!trimmed) return "";
|
|
629
|
-
const lower = trimmed.toLowerCase();
|
|
630
|
-
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
|
|
631
|
-
return "";
|
|
632
|
-
}
|
|
633
|
-
return trimmed;
|
|
634
|
-
}
|
|
635
|
-
function sanitizeCSSValue(value) {
|
|
636
|
-
const lower = value.toLowerCase().replace(/\s+/g, "");
|
|
637
|
-
if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
|
|
638
|
-
return "";
|
|
639
|
-
}
|
|
640
|
-
return value;
|
|
641
|
-
}
|
|
642
|
-
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
|
|
643
|
-
function isUrlAttribute(attr) {
|
|
644
|
-
return URL_ATTRIBUTES.has(attr);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
851
|
// src/platform/head.ts
|
|
648
|
-
var HEAD_URL_ATTRS = /* @__PURE__ */ new Set(["href", "src"
|
|
852
|
+
var HEAD_URL_ATTRS = /* @__PURE__ */ new Set(["href", "src"]);
|
|
649
853
|
function sanitizeHeadAttr(key, value) {
|
|
650
854
|
if (HEAD_URL_ATTRS.has(key)) return sanitizeUrl(value);
|
|
651
855
|
return value;
|
|
652
856
|
}
|
|
857
|
+
function isDangerousMetaRefresh2(metaProps) {
|
|
858
|
+
const httpEquiv = metaProps["http-equiv"];
|
|
859
|
+
if (typeof httpEquiv !== "string") return false;
|
|
860
|
+
if (httpEquiv.toLowerCase() !== "refresh") return false;
|
|
861
|
+
const content = metaProps.content;
|
|
862
|
+
if (typeof content !== "string") return false;
|
|
863
|
+
const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
|
|
864
|
+
return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
|
|
865
|
+
}
|
|
866
|
+
var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
867
|
+
function isEventHandlerAttr2(name) {
|
|
868
|
+
if (name.length < 3) return false;
|
|
869
|
+
const lower = name.toLowerCase();
|
|
870
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
871
|
+
}
|
|
872
|
+
function isSafeHeadAttr(name) {
|
|
873
|
+
if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
|
|
874
|
+
if (isEventHandlerAttr2(name)) return false;
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
function escapeScriptJsonLocal(json) {
|
|
878
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
879
|
+
}
|
|
653
880
|
function Head(props) {
|
|
654
881
|
const anchor = document.createComment("sibu-head");
|
|
655
882
|
const managedElements = [];
|
|
@@ -676,15 +903,17 @@ function Head(props) {
|
|
|
676
903
|
}
|
|
677
904
|
if (props.meta) {
|
|
678
905
|
for (const metaProps of props.meta) {
|
|
906
|
+
if (isDangerousMetaRefresh2(metaProps)) continue;
|
|
679
907
|
const el = document.createElement("meta");
|
|
680
908
|
for (const [key, value] of Object.entries(metaProps)) {
|
|
909
|
+
if (!isSafeHeadAttr(key)) continue;
|
|
681
910
|
if (typeof value === "function") {
|
|
682
911
|
const cleanupFn = effect(() => {
|
|
683
|
-
el.setAttribute(key, value());
|
|
912
|
+
el.setAttribute(key, sanitizeHeadAttr(key, value()));
|
|
684
913
|
});
|
|
685
914
|
effectCleanups.push(cleanupFn);
|
|
686
915
|
} else {
|
|
687
|
-
el.setAttribute(key, value);
|
|
916
|
+
el.setAttribute(key, sanitizeHeadAttr(key, value));
|
|
688
917
|
}
|
|
689
918
|
}
|
|
690
919
|
document.head.appendChild(el);
|
|
@@ -695,6 +924,7 @@ function Head(props) {
|
|
|
695
924
|
for (const linkProps of props.link) {
|
|
696
925
|
const el = document.createElement("link");
|
|
697
926
|
for (const [key, value] of Object.entries(linkProps)) {
|
|
927
|
+
if (!isSafeHeadAttr(key)) continue;
|
|
698
928
|
el.setAttribute(key, sanitizeHeadAttr(key, value));
|
|
699
929
|
}
|
|
700
930
|
document.head.appendChild(el);
|
|
@@ -705,6 +935,7 @@ function Head(props) {
|
|
|
705
935
|
for (const scriptProps of props.script) {
|
|
706
936
|
const el = document.createElement("script");
|
|
707
937
|
for (const [key, value] of Object.entries(scriptProps)) {
|
|
938
|
+
if (!isSafeHeadAttr(key)) continue;
|
|
708
939
|
el.setAttribute(key, sanitizeHeadAttr(key, value));
|
|
709
940
|
}
|
|
710
941
|
document.head.appendChild(el);
|
|
@@ -715,7 +946,10 @@ function Head(props) {
|
|
|
715
946
|
const existing = document.head.querySelector("base");
|
|
716
947
|
if (existing) existing.remove();
|
|
717
948
|
const el = document.createElement("base");
|
|
718
|
-
if (props.base.href)
|
|
949
|
+
if (props.base.href) {
|
|
950
|
+
const safeHref = sanitizeUrl(props.base.href);
|
|
951
|
+
if (safeHref) el.href = safeHref;
|
|
952
|
+
}
|
|
719
953
|
if (props.base.target) el.target = props.base.target;
|
|
720
954
|
document.head.appendChild(el);
|
|
721
955
|
managedElements.push(el);
|
|
@@ -730,7 +964,7 @@ function setStructuredData(data2) {
|
|
|
730
964
|
const script2 = document.createElement("script");
|
|
731
965
|
script2.type = "application/ld+json";
|
|
732
966
|
script2.setAttribute("data-sibu", "true");
|
|
733
|
-
script2.textContent = JSON.stringify(data2);
|
|
967
|
+
script2.textContent = escapeScriptJsonLocal(JSON.stringify(data2));
|
|
734
968
|
document.head.appendChild(script2);
|
|
735
969
|
}
|
|
736
970
|
function setCanonical(url) {
|
|
@@ -957,7 +1191,20 @@ function createMiddlewareChain() {
|
|
|
957
1191
|
|
|
958
1192
|
// src/reactivity/bindAttribute.ts
|
|
959
1193
|
var _isDev5 = isDev();
|
|
1194
|
+
function isEventHandlerAttr3(name) {
|
|
1195
|
+
if (name.length < 3) return false;
|
|
1196
|
+
const lower = name.toLowerCase();
|
|
1197
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
1198
|
+
}
|
|
960
1199
|
function bindAttribute(el, attr, getter) {
|
|
1200
|
+
if (isEventHandlerAttr3(attr)) {
|
|
1201
|
+
if (_isDev5)
|
|
1202
|
+
devWarn(
|
|
1203
|
+
`bindAttribute: refusing to bind event-handler attribute "${attr}". Use on:{ ${attr.slice(2)}: fn } instead.`
|
|
1204
|
+
);
|
|
1205
|
+
return () => {
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
961
1208
|
function commit() {
|
|
962
1209
|
let value;
|
|
963
1210
|
try {
|
|
@@ -1200,16 +1447,20 @@ function appendChildren(el, nodes) {
|
|
|
1200
1447
|
var tagFactory = (tag, ns) => (first, second) => {
|
|
1201
1448
|
const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
|
|
1202
1449
|
if (first === void 0) return el;
|
|
1203
|
-
if (
|
|
1450
|
+
if (typeof first === "string") {
|
|
1451
|
+
if (second !== void 0) {
|
|
1452
|
+
el.setAttribute("class", first);
|
|
1453
|
+
appendChildren(el, second);
|
|
1454
|
+
return el;
|
|
1455
|
+
}
|
|
1204
1456
|
el.textContent = first;
|
|
1205
1457
|
return el;
|
|
1206
1458
|
}
|
|
1207
|
-
if (
|
|
1208
|
-
el.
|
|
1209
|
-
appendChildren(el, second);
|
|
1459
|
+
if (typeof first === "number") {
|
|
1460
|
+
el.textContent = String(first);
|
|
1210
1461
|
return el;
|
|
1211
1462
|
}
|
|
1212
|
-
if (Array.isArray(first) || first instanceof Node) {
|
|
1463
|
+
if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
|
|
1213
1464
|
appendChildren(el, first);
|
|
1214
1465
|
return el;
|
|
1215
1466
|
}
|
|
@@ -1218,7 +1469,7 @@ var tagFactory = (tag, ns) => (first, second) => {
|
|
|
1218
1469
|
if (pClass != null) applyClass(el, pClass);
|
|
1219
1470
|
const pId = props.id;
|
|
1220
1471
|
if (pId != null) el.id = pId;
|
|
1221
|
-
const pNodes = props.nodes;
|
|
1472
|
+
const pNodes = second !== void 0 ? second : props.nodes;
|
|
1222
1473
|
if (pNodes != null) appendChildren(el, pNodes);
|
|
1223
1474
|
const pOn = props.on;
|
|
1224
1475
|
if (pOn) {
|
|
@@ -1846,6 +2097,7 @@ function isWasmCached(key) {
|
|
|
1846
2097
|
createWorkerPool,
|
|
1847
2098
|
defineRemoteComponent,
|
|
1848
2099
|
deserializeState,
|
|
2100
|
+
escapeScriptJson,
|
|
1849
2101
|
generateStaticSite,
|
|
1850
2102
|
hydrate,
|
|
1851
2103
|
hydrateIslands,
|
package/dist/ssr.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { T as TrustedHTML, c as collectStream, d as deserializeState, h as hydrate,
|
|
1
|
+
export { H as HydrateOptions, a as HydrationMismatch, T as TrustedHTML, c as collectStream, d as deserializeState, e as escapeScriptJson, h as hydrate, b as hydrateIslands, f as hydrateProgressively, i as island, r as renderToDocument, g as renderToReadableStream, j as renderToStream, k as renderToString, l as renderToSuspenseStream, m as resetSSRState, s as serializeState, n as ssrSuspense, o as suspenseSwapScript, t as trustHTML } from './ssr-Do_SiVoL.cjs';
|
|
2
2
|
|
|
3
3
|
interface HeadProps {
|
|
4
4
|
title?: string | (() => string);
|
|
@@ -18,6 +18,15 @@ interface HeadProps {
|
|
|
18
18
|
declare function Head(props: HeadProps): Comment;
|
|
19
19
|
/**
|
|
20
20
|
* Sets structured data (JSON-LD) for SEO.
|
|
21
|
+
*
|
|
22
|
+
* Security: the serialized JSON is passed through `escapeScriptJsonLocal`
|
|
23
|
+
* which unicode-escapes `<`, `>`, `&`, `U+2028`, and `U+2029`. This is
|
|
24
|
+
* defense-in-depth: when the element is inserted via `document.createElement`
|
|
25
|
+
* + `textContent` the browser will NOT re-parse the body, so `</script>`
|
|
26
|
+
* cannot break out of the tag at insertion time. However, tools that
|
|
27
|
+
* later serialize `document.head.innerHTML` DO re-parse, and the server
|
|
28
|
+
* side of any SSR roundtrip would see the raw text. Escaping here makes
|
|
29
|
+
* both paths safe.
|
|
21
30
|
*/
|
|
22
31
|
declare function setStructuredData(data: Record<string, unknown>): void;
|
|
23
32
|
/**
|
package/dist/ssr.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { T as TrustedHTML, c as collectStream, d as deserializeState, h as hydrate,
|
|
1
|
+
export { H as HydrateOptions, a as HydrationMismatch, T as TrustedHTML, c as collectStream, d as deserializeState, e as escapeScriptJson, h as hydrate, b as hydrateIslands, f as hydrateProgressively, i as island, r as renderToDocument, g as renderToReadableStream, j as renderToStream, k as renderToString, l as renderToSuspenseStream, m as resetSSRState, s as serializeState, n as ssrSuspense, o as suspenseSwapScript, t as trustHTML } from './ssr-Do_SiVoL.js';
|
|
2
2
|
|
|
3
3
|
interface HeadProps {
|
|
4
4
|
title?: string | (() => string);
|
|
@@ -18,6 +18,15 @@ interface HeadProps {
|
|
|
18
18
|
declare function Head(props: HeadProps): Comment;
|
|
19
19
|
/**
|
|
20
20
|
* Sets structured data (JSON-LD) for SEO.
|
|
21
|
+
*
|
|
22
|
+
* Security: the serialized JSON is passed through `escapeScriptJsonLocal`
|
|
23
|
+
* which unicode-escapes `<`, `>`, `&`, `U+2028`, and `U+2029`. This is
|
|
24
|
+
* defense-in-depth: when the element is inserted via `document.createElement`
|
|
25
|
+
* + `textContent` the browser will NOT re-parse the body, so `</script>`
|
|
26
|
+
* cannot break out of the tag at insertion time. However, tools that
|
|
27
|
+
* later serialize `document.head.innerHTML` DO re-parse, and the server
|
|
28
|
+
* side of any SSR roundtrip would see the raw text. Escaping here makes
|
|
29
|
+
* both paths safe.
|
|
21
30
|
*/
|
|
22
31
|
declare function setStructuredData(data: Record<string, unknown>): void;
|
|
23
32
|
/**
|
package/dist/ssr.js
CHANGED
|
@@ -22,10 +22,11 @@ import {
|
|
|
22
22
|
wasm,
|
|
23
23
|
worker,
|
|
24
24
|
workerFn
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-2BYQDGN3.js";
|
|
26
26
|
import {
|
|
27
27
|
collectStream,
|
|
28
28
|
deserializeState,
|
|
29
|
+
escapeScriptJson,
|
|
29
30
|
hydrate,
|
|
30
31
|
hydrateIslands,
|
|
31
32
|
hydrateProgressively,
|
|
@@ -40,15 +41,16 @@ import {
|
|
|
40
41
|
ssrSuspense,
|
|
41
42
|
suspenseSwapScript,
|
|
42
43
|
trustHTML
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
import "./chunk-
|
|
45
|
-
import "./chunk-
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-
|
|
48
|
-
import "./chunk-
|
|
49
|
-
import "./chunk-
|
|
50
|
-
import "./chunk-
|
|
51
|
-
import "./chunk-
|
|
44
|
+
} from "./chunk-3X2YG6YM.js";
|
|
45
|
+
import "./chunk-32DY64NT.js";
|
|
46
|
+
import "./chunk-F3FA4F32.js";
|
|
47
|
+
import "./chunk-PTQJDMRT.js";
|
|
48
|
+
import "./chunk-CMBFNA7L.js";
|
|
49
|
+
import "./chunk-CHF5OHIA.js";
|
|
50
|
+
import "./chunk-EUZND3CB.js";
|
|
51
|
+
import "./chunk-WZSPOOER.js";
|
|
52
|
+
import "./chunk-ZD6OAMTH.js";
|
|
53
|
+
import "./chunk-5X6PP2UK.js";
|
|
52
54
|
export {
|
|
53
55
|
Head,
|
|
54
56
|
clearWasmCache,
|
|
@@ -63,6 +65,7 @@ export {
|
|
|
63
65
|
createWorkerPool,
|
|
64
66
|
defineRemoteComponent,
|
|
65
67
|
deserializeState,
|
|
68
|
+
escapeScriptJson,
|
|
66
69
|
generateStaticSite,
|
|
67
70
|
hydrate,
|
|
68
71
|
hydrateIslands,
|