sibujs 3.4.0 → 4.0.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/LICENSE +21 -21
- package/README.md +118 -15
- package/dist/browser.cjs +410 -101
- package/dist/browser.d.cts +50 -5
- package/dist/browser.d.ts +50 -5
- package/dist/browser.js +8 -6
- package/dist/build.cjs +777 -385
- package/dist/build.js +15 -15
- package/dist/cdn.global.js +11 -7
- package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
- package/dist/chunk-4MFZMLUU.js +147 -0
- package/dist/chunk-52XFPGSN.js +123 -0
- package/dist/chunk-5INI7D2L.js +194 -0
- package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
- package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
- package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
- package/dist/chunk-DHDZ7IVN.js +109 -0
- package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
- package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
- package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
- package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
- package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
- package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
- package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
- package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
- package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
- package/dist/chunk-O3QQ3XOL.js +61 -0
- package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
- package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
- package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
- package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
- package/dist/chunk-VPP2FONR.js +142 -0
- package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
- package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
- package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
- package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
- package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
- package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
- package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
- package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
- package/dist/data.cjs +483 -205
- package/dist/data.d.cts +139 -7
- package/dist/data.d.ts +139 -7
- package/dist/data.js +9 -9
- package/dist/devtools.cjs +222 -87
- package/dist/devtools.js +8 -8
- package/dist/dispose-4FZ6OFXR.js +19 -0
- package/dist/ecosystem.cjs +419 -175
- package/dist/ecosystem.js +11 -11
- package/dist/extras.cjs +1981 -765
- package/dist/extras.d.cts +7 -7
- package/dist/extras.d.ts +7 -7
- package/dist/extras.js +29 -26
- package/dist/index.cjs +784 -385
- package/dist/index.d.cts +299 -28
- package/dist/index.d.ts +299 -28
- package/dist/index.js +33 -18
- package/dist/motion.cjs +180 -50
- package/dist/motion.d.cts +26 -0
- package/dist/motion.d.ts +26 -0
- package/dist/motion.js +5 -5
- package/dist/patterns.cjs +437 -170
- package/dist/patterns.d.cts +60 -13
- package/dist/patterns.d.ts +60 -13
- package/dist/patterns.js +7 -7
- package/dist/performance.cjs +333 -148
- package/dist/performance.d.cts +55 -7
- package/dist/performance.d.ts +55 -7
- package/dist/performance.js +9 -9
- package/dist/plugins.cjs +1374 -576
- package/dist/plugins.d.cts +227 -9
- package/dist/plugins.d.ts +227 -9
- package/dist/plugins.js +613 -321
- package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
- package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
- package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
- package/dist/ssr.cjs +1104 -443
- package/dist/ssr.d.cts +230 -20
- package/dist/ssr.d.ts +230 -20
- package/dist/ssr.js +15 -12
- package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
- package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
- package/dist/testing.cjs +205 -63
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +8 -10
- package/dist/ui.cjs +640 -199
- package/dist/ui.d.cts +2 -2
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +10 -10
- package/dist/widgets.cjs +207 -82
- package/dist/widgets.js +9 -9
- package/package.json +12 -7
- package/dist/chunk-5VGSK6D2.js +0 -80
- package/dist/chunk-COY6PUD2.js +0 -34
- package/dist/chunk-K35BAR4P.js +0 -96
- package/dist/chunk-L3GAGWCC.js +0 -111
- package/dist/chunk-RT3IYFN2.js +0 -38
|
@@ -1,39 +1,195 @@
|
|
|
1
1
|
import {
|
|
2
|
+
canonicalAttrName,
|
|
2
3
|
isEventHandlerAttr,
|
|
3
|
-
|
|
4
|
+
isHtmlContentAttribute,
|
|
5
|
+
isPolicyAttribute,
|
|
6
|
+
sanitizeAttributeString,
|
|
4
7
|
sanitizeUrl,
|
|
5
8
|
stripControlChars
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5INI7D2L.js";
|
|
7
10
|
import {
|
|
8
11
|
getSSRStore
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-TIRZCERI.js";
|
|
13
|
+
import {
|
|
14
|
+
dispose,
|
|
15
|
+
replaceChildrenSafely
|
|
16
|
+
} from "./chunk-DHDZ7IVN.js";
|
|
10
17
|
import {
|
|
11
18
|
isDev
|
|
12
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-VPP2FONR.js";
|
|
20
|
+
|
|
21
|
+
// src/utils/metaRefresh.ts
|
|
22
|
+
function isAsciiWhitespace(ch) {
|
|
23
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
|
|
24
|
+
}
|
|
25
|
+
function trimAsciiWhitespace(value) {
|
|
26
|
+
let start = 0;
|
|
27
|
+
let end = value.length;
|
|
28
|
+
while (start < end && isAsciiWhitespace(value[start])) start++;
|
|
29
|
+
while (end > start && isAsciiWhitespace(value[end - 1])) end--;
|
|
30
|
+
return value.slice(start, end);
|
|
31
|
+
}
|
|
32
|
+
var forbidden = (reason) => ({ kind: "forbidden", reason });
|
|
33
|
+
function parseMetaRefreshContent(content) {
|
|
34
|
+
const raw = trimAsciiWhitespace(stripControlChars(content));
|
|
35
|
+
if (raw === "") return forbidden("empty content");
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < raw.length && raw[i] >= "0" && raw[i] <= "9") i++;
|
|
38
|
+
if (i === 0) return forbidden("missing or non-numeric delay");
|
|
39
|
+
const delay = Number.parseInt(raw.slice(0, i), 10);
|
|
40
|
+
if (!Number.isFinite(delay)) return forbidden("unparseable delay");
|
|
41
|
+
while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
|
|
42
|
+
if (i === raw.length) return { kind: "delay-only", delay };
|
|
43
|
+
if (raw[i] !== ";" && raw[i] !== ",") return forbidden("unexpected text after delay");
|
|
44
|
+
i++;
|
|
45
|
+
while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
|
|
46
|
+
const keyStart = i;
|
|
47
|
+
while (i < raw.length && !isAsciiWhitespace(raw[i]) && raw[i] !== "=") i++;
|
|
48
|
+
const key = raw.slice(keyStart, i).toLowerCase();
|
|
49
|
+
if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
|
|
50
|
+
while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
|
|
51
|
+
if (raw[i] !== "=") return forbidden("missing '=' after url");
|
|
52
|
+
i++;
|
|
53
|
+
while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
|
|
54
|
+
let destination;
|
|
55
|
+
const quote = raw[i];
|
|
56
|
+
if (quote === '"' || quote === "'") {
|
|
57
|
+
const close = raw.indexOf(quote, i + 1);
|
|
58
|
+
if (close === -1) return forbidden("unterminated quoted destination");
|
|
59
|
+
destination = raw.slice(i + 1, close);
|
|
60
|
+
const rest = trimAsciiWhitespace(raw.slice(close + 1));
|
|
61
|
+
if (rest !== "") return forbidden("trailing text after quoted destination");
|
|
62
|
+
} else {
|
|
63
|
+
destination = trimAsciiWhitespace(raw.slice(i));
|
|
64
|
+
if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
|
|
65
|
+
}
|
|
66
|
+
if (destination === "") return forbidden("empty destination");
|
|
67
|
+
const sanitizedDestination = sanitizeUrl(destination);
|
|
68
|
+
if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
|
|
69
|
+
return { kind: "allowed", delay, destination, sanitizedDestination };
|
|
70
|
+
}
|
|
71
|
+
function resolveMetaRefreshPolicy(attributes) {
|
|
72
|
+
let httpEquiv;
|
|
73
|
+
let content;
|
|
74
|
+
let duplicateName;
|
|
75
|
+
const seen = /* @__PURE__ */ new Set();
|
|
76
|
+
for (const [key, value] of attributes) {
|
|
77
|
+
const canonical = canonicalAttrName(key);
|
|
78
|
+
if (seen.has(canonical)) duplicateName = canonical;
|
|
79
|
+
seen.add(canonical);
|
|
80
|
+
if (canonical === "http-equiv") httpEquiv = value;
|
|
81
|
+
else if (canonical === "content") content = value;
|
|
82
|
+
}
|
|
83
|
+
if (duplicateName !== void 0) {
|
|
84
|
+
return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
|
|
85
|
+
}
|
|
86
|
+
if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
|
|
87
|
+
if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
|
|
88
|
+
return { kind: "not-refresh" };
|
|
89
|
+
}
|
|
90
|
+
if (typeof content !== "string") return { kind: "not-refresh" };
|
|
91
|
+
return parseMetaRefreshContent(content);
|
|
92
|
+
}
|
|
93
|
+
function normalizeMetaAttributes(props) {
|
|
94
|
+
const out = /* @__PURE__ */ new Map();
|
|
95
|
+
const seen = /* @__PURE__ */ new Set();
|
|
96
|
+
for (const key of Object.keys(props)) {
|
|
97
|
+
const canonical = canonicalAttrName(key);
|
|
98
|
+
if (seen.has(canonical)) return null;
|
|
99
|
+
seen.add(canonical);
|
|
100
|
+
out.set(key, props[key]);
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function isForbiddenMetaEntry(props) {
|
|
105
|
+
const normalized = normalizeMetaAttributes(props);
|
|
106
|
+
if (normalized === null) return true;
|
|
107
|
+
return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/utils/headEntry.ts
|
|
111
|
+
var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
112
|
+
function isEmittableHeadAttrName(name) {
|
|
113
|
+
if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
|
|
114
|
+
const canonical = canonicalAttrName(name);
|
|
115
|
+
if (isEventHandlerAttr(canonical)) return false;
|
|
116
|
+
if (isHtmlContentAttribute(canonical)) return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
function sanitizeHeadAttrValue(canonical, value) {
|
|
120
|
+
if (!isPolicyAttribute(canonical)) return value;
|
|
121
|
+
const safe = sanitizeAttributeString(canonical, value);
|
|
122
|
+
return safe === "" ? null : safe;
|
|
123
|
+
}
|
|
124
|
+
function findDuplicateAttributeName(names) {
|
|
125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
126
|
+
for (const name of names) {
|
|
127
|
+
const canonical = canonicalAttrName(name);
|
|
128
|
+
if (seen.has(canonical)) return canonical;
|
|
129
|
+
seen.add(canonical);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function planEntry(props, resolver, applyRefreshPolicy) {
|
|
134
|
+
const rawNames = Object.keys(props);
|
|
135
|
+
const duplicate = findDuplicateAttributeName(rawNames);
|
|
136
|
+
if (duplicate !== null) {
|
|
137
|
+
return { kind: "drop", reason: `duplicate case-insensitive attribute ${JSON.stringify(duplicate)}` };
|
|
138
|
+
}
|
|
139
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
140
|
+
let reactive = false;
|
|
141
|
+
for (const name of rawNames) {
|
|
142
|
+
if (!Object.hasOwn(props, name)) continue;
|
|
143
|
+
if (!isEmittableHeadAttrName(name)) continue;
|
|
144
|
+
const authored = props[name];
|
|
145
|
+
if (resolver.isReactiveValue?.(authored)) reactive = true;
|
|
146
|
+
const canonical = canonicalAttrName(name);
|
|
147
|
+
const sanitized = sanitizeHeadAttrValue(canonical, resolver.resolveValue(name, authored));
|
|
148
|
+
if (sanitized === null) continue;
|
|
149
|
+
attributes.set(canonical, sanitized);
|
|
150
|
+
}
|
|
151
|
+
if (attributes.size === 0) return { kind: "drop", reason: "no effective attributes remain" };
|
|
152
|
+
const refresh = applyRefreshPolicy ? resolveMetaRefreshPolicy(attributes) : { kind: "not-refresh" };
|
|
153
|
+
if (refresh.kind === "forbidden") return { kind: "drop", reason: refresh.reason };
|
|
154
|
+
if (reactive && (refresh.kind === "allowed" || refresh.kind === "delay-only")) {
|
|
155
|
+
return { kind: "drop", reason: "a reactive meta entry may not publish a native refresh directive" };
|
|
156
|
+
}
|
|
157
|
+
return { kind: "publish", attributes, refresh };
|
|
158
|
+
}
|
|
159
|
+
function planMetaEntry(props, resolver) {
|
|
160
|
+
return planEntry(props, resolver, true);
|
|
161
|
+
}
|
|
162
|
+
function planHeadElementEntry(props, resolver) {
|
|
163
|
+
return planEntry(props, resolver, false);
|
|
164
|
+
}
|
|
165
|
+
var STATIC_VALUE_RESOLVER = {
|
|
166
|
+
resolveValue: (_name, value) => String(value)
|
|
167
|
+
};
|
|
168
|
+
function escapeHeadAttrValue(value) {
|
|
169
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
170
|
+
}
|
|
171
|
+
function serializeHeadEntry(tag, attrs) {
|
|
172
|
+
const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
|
|
173
|
+
if (plan.kind === "drop") return "";
|
|
174
|
+
const pairs = Array.from(plan.attributes, ([name, value]) => `${name}="${escapeHeadAttrValue(value)}"`).join(" ");
|
|
175
|
+
return `<${tag} ${pairs} />`;
|
|
176
|
+
}
|
|
13
177
|
|
|
14
178
|
// src/platform/ssr.ts
|
|
15
|
-
function
|
|
16
|
-
return
|
|
179
|
+
function isPolicyAttr(lowerName) {
|
|
180
|
+
return isPolicyAttribute(lowerName);
|
|
181
|
+
}
|
|
182
|
+
function isForbiddenSsrAttr(lowerName) {
|
|
183
|
+
return isHtmlContentAttribute(lowerName);
|
|
184
|
+
}
|
|
185
|
+
function sanitizeSsrAttributeValue(lowerName, value) {
|
|
186
|
+
return sanitizeAttributeString(lowerName, value);
|
|
17
187
|
}
|
|
18
188
|
var _isDev = isDev();
|
|
19
189
|
var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
20
190
|
function isSafeAttrName(name) {
|
|
21
191
|
return SAFE_ATTR_NAME.test(name);
|
|
22
192
|
}
|
|
23
|
-
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
24
|
-
"href",
|
|
25
|
-
"src",
|
|
26
|
-
"action",
|
|
27
|
-
"formaction",
|
|
28
|
-
"cite",
|
|
29
|
-
"poster",
|
|
30
|
-
"background",
|
|
31
|
-
"srcset",
|
|
32
|
-
"ping",
|
|
33
|
-
"manifest",
|
|
34
|
-
"data",
|
|
35
|
-
"xlink:href"
|
|
36
|
-
]);
|
|
37
193
|
function ssrErrorComment(err) {
|
|
38
194
|
if (_isDev) {
|
|
39
195
|
const msg = escapeHtml(err instanceof Error ? err.message : String(err));
|
|
@@ -91,10 +247,11 @@ function renderToString(element) {
|
|
|
91
247
|
const rawName = attr.name;
|
|
92
248
|
if (!isSafeAttrName(rawName)) continue;
|
|
93
249
|
if (isEventHandlerAttr(rawName)) continue;
|
|
250
|
+
if (isForbiddenSsrAttr(rawName)) continue;
|
|
94
251
|
const lowerName = rawName.toLowerCase();
|
|
95
252
|
let value = attr.value;
|
|
96
|
-
if (
|
|
97
|
-
value =
|
|
253
|
+
if (isPolicyAttr(lowerName)) {
|
|
254
|
+
value = sanitizeSsrAttributeValue(lowerName, value);
|
|
98
255
|
if (!value) continue;
|
|
99
256
|
}
|
|
100
257
|
html += ` ${rawName}="${escapeAttr(value)}"`;
|
|
@@ -135,9 +292,20 @@ function hydrate(component, container, options = {}) {
|
|
|
135
292
|
}
|
|
136
293
|
}
|
|
137
294
|
}
|
|
138
|
-
container
|
|
295
|
+
replaceChildrenSafely(container, clientTree);
|
|
139
296
|
container.setAttribute("data-sibu-hydrated", "true");
|
|
140
297
|
}
|
|
298
|
+
function directTextOf(node) {
|
|
299
|
+
let out = "";
|
|
300
|
+
for (const child of Array.from(node.childNodes)) {
|
|
301
|
+
if (child.nodeType === 3) out += child.nodeValue ?? "";
|
|
302
|
+
}
|
|
303
|
+
return out.replace(/\s+/g, " ").trim();
|
|
304
|
+
}
|
|
305
|
+
function disposeAndReplace(outgoing, next) {
|
|
306
|
+
dispose(outgoing);
|
|
307
|
+
outgoing.replaceWith(next);
|
|
308
|
+
}
|
|
141
309
|
function collectMismatches(serverNode, clientNode, path, out, max = 5) {
|
|
142
310
|
if (out.length >= max) return;
|
|
143
311
|
const nodePath = path || clientNode?.tagName?.toLowerCase() || "(root)";
|
|
@@ -213,6 +381,18 @@ function collectMismatches(serverNode, clientNode, path, out, max = 5) {
|
|
|
213
381
|
});
|
|
214
382
|
}
|
|
215
383
|
}
|
|
384
|
+
const serverText = directTextOf(serverNode);
|
|
385
|
+
const clientText = directTextOf(clientNode);
|
|
386
|
+
if (serverText !== clientText) {
|
|
387
|
+
out.push({
|
|
388
|
+
kind: "text",
|
|
389
|
+
path: nodePath,
|
|
390
|
+
serverValue: serverText,
|
|
391
|
+
clientValue: clientText,
|
|
392
|
+
message: "Text content differs between server and client."
|
|
393
|
+
});
|
|
394
|
+
if (out.length >= max) return;
|
|
395
|
+
}
|
|
216
396
|
const serverChildren = Array.from(serverNode.children);
|
|
217
397
|
const clientChildren = Array.from(clientNode.children);
|
|
218
398
|
const max2 = Math.max(serverChildren.length, clientChildren.length);
|
|
@@ -232,10 +412,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
|
232
412
|
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
233
413
|
if (!isSafeAttrName(rawKey)) continue;
|
|
234
414
|
if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
|
|
415
|
+
if (isForbiddenSsrAttr(rawKey)) continue;
|
|
235
416
|
const lowerKey = rawKey.toLowerCase();
|
|
236
417
|
let value = String(attrs[rawKey]);
|
|
237
|
-
if (
|
|
238
|
-
value =
|
|
418
|
+
if (isPolicyAttr(lowerKey)) {
|
|
419
|
+
value = sanitizeSsrAttributeValue(lowerKey, value);
|
|
239
420
|
if (!value) continue;
|
|
240
421
|
}
|
|
241
422
|
out.push(`${rawKey}="${escapeAttr(value)}"`);
|
|
@@ -243,18 +424,7 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
|
243
424
|
return out.join(" ");
|
|
244
425
|
}
|
|
245
426
|
function isDangerousMetaRefresh(metaProps) {
|
|
246
|
-
|
|
247
|
-
let content;
|
|
248
|
-
for (const k in metaProps) {
|
|
249
|
-
const lk = k.toLowerCase();
|
|
250
|
-
if (lk === "http-equiv") httpEquiv = metaProps[k];
|
|
251
|
-
else if (lk === "content") content = metaProps[k];
|
|
252
|
-
}
|
|
253
|
-
if (typeof httpEquiv !== "string") return false;
|
|
254
|
-
if (httpEquiv.toLowerCase() !== "refresh") return false;
|
|
255
|
-
if (typeof content !== "string") return false;
|
|
256
|
-
const normalized = stripControlChars(content).toLowerCase();
|
|
257
|
-
return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
|
|
427
|
+
return isForbiddenMetaEntry(metaProps);
|
|
258
428
|
}
|
|
259
429
|
function renderToDocument(component, options = {}) {
|
|
260
430
|
let content;
|
|
@@ -263,15 +433,8 @@ function renderToDocument(component, options = {}) {
|
|
|
263
433
|
} catch (err) {
|
|
264
434
|
content = ssrErrorComment(err);
|
|
265
435
|
}
|
|
266
|
-
const metaTags = (options.meta || []).map((attrs) =>
|
|
267
|
-
|
|
268
|
-
const pairs = buildAttrString(attrs);
|
|
269
|
-
return pairs ? `<meta ${pairs} />` : "";
|
|
270
|
-
}).filter(Boolean).join("\n ");
|
|
271
|
-
const linkTags = (options.links || []).map((attrs) => {
|
|
272
|
-
const pairs = buildAttrString(attrs);
|
|
273
|
-
return pairs ? `<link ${pairs} />` : "";
|
|
274
|
-
}).filter(Boolean).join("\n ");
|
|
436
|
+
const metaTags = (options.meta || []).map((attrs) => serializeHeadEntry("meta", attrs)).filter(Boolean).join("\n ");
|
|
437
|
+
const linkTags = (options.links || []).map((attrs) => serializeHeadEntry("link", attrs)).filter(Boolean).join("\n ");
|
|
275
438
|
const scriptTags = (options.scripts || []).map((src) => {
|
|
276
439
|
const safe = sanitizeUrl(String(src));
|
|
277
440
|
if (!safe) return "";
|
|
@@ -332,14 +495,18 @@ async function* renderToStream(element) {
|
|
|
332
495
|
const rawName = attr.name;
|
|
333
496
|
if (!isSafeAttrName(rawName)) continue;
|
|
334
497
|
if (isEventHandlerAttr(rawName)) continue;
|
|
498
|
+
if (isForbiddenSsrAttr(rawName)) continue;
|
|
335
499
|
const lowerName = rawName.toLowerCase();
|
|
336
500
|
let value = attr.value;
|
|
337
|
-
if (
|
|
338
|
-
value =
|
|
501
|
+
if (isPolicyAttr(lowerName)) {
|
|
502
|
+
value = sanitizeSsrAttributeValue(lowerName, value);
|
|
339
503
|
if (!value) continue;
|
|
340
504
|
}
|
|
341
505
|
openTag += ` ${rawName}="${escapeAttr(value)}"`;
|
|
342
506
|
}
|
|
507
|
+
if (element.dataset && !element.dataset.sibuHydrate) {
|
|
508
|
+
openTag += ` data-sibu-ssr="true"`;
|
|
509
|
+
}
|
|
343
510
|
if (VOID_ELEMENTS.has(tag)) {
|
|
344
511
|
yield `${openTag} />`;
|
|
345
512
|
return;
|
|
@@ -386,9 +553,13 @@ function island(id, component) {
|
|
|
386
553
|
el.setAttribute("data-sibu-island", id);
|
|
387
554
|
return el;
|
|
388
555
|
}
|
|
556
|
+
function isAlreadyHydrated(element) {
|
|
557
|
+
return element.hasAttribute("data-sibu-hydrated");
|
|
558
|
+
}
|
|
389
559
|
function hydrateIslands(container, islands) {
|
|
390
560
|
const markers = container.querySelectorAll("[data-sibu-island]");
|
|
391
561
|
for (const marker of Array.from(markers)) {
|
|
562
|
+
if (isAlreadyHydrated(marker)) continue;
|
|
392
563
|
const id = marker.getAttribute("data-sibu-island") ?? "";
|
|
393
564
|
if (!Object.hasOwn(islands, id)) continue;
|
|
394
565
|
const factory = islands[id];
|
|
@@ -396,7 +567,7 @@ function hydrateIslands(container, islands) {
|
|
|
396
567
|
const clientTree = factory();
|
|
397
568
|
clientTree.setAttribute("data-sibu-island", id);
|
|
398
569
|
clientTree.setAttribute("data-sibu-hydrated", "true");
|
|
399
|
-
marker
|
|
570
|
+
disposeAndReplace(marker, clientTree);
|
|
400
571
|
}
|
|
401
572
|
container.setAttribute("data-sibu-hydrated", "partial");
|
|
402
573
|
}
|
|
@@ -404,6 +575,7 @@ function hydrateProgressively(container, islands, options) {
|
|
|
404
575
|
const markers = container.querySelectorAll("[data-sibu-island]");
|
|
405
576
|
const cleanups = [];
|
|
406
577
|
for (const marker of Array.from(markers)) {
|
|
578
|
+
if (isAlreadyHydrated(marker)) continue;
|
|
407
579
|
const id = marker.getAttribute("data-sibu-island") ?? "";
|
|
408
580
|
if (!Object.hasOwn(islands, id)) continue;
|
|
409
581
|
const factory = islands[id];
|
|
@@ -413,10 +585,11 @@ function hydrateProgressively(container, islands, options) {
|
|
|
413
585
|
for (const entry of entries) {
|
|
414
586
|
if (entry.isIntersecting) {
|
|
415
587
|
observer.disconnect();
|
|
588
|
+
if (isAlreadyHydrated(marker) || marker.parentNode === null) break;
|
|
416
589
|
const clientTree = factory();
|
|
417
590
|
clientTree.setAttribute("data-sibu-island", id);
|
|
418
591
|
clientTree.setAttribute("data-sibu-hydrated", "true");
|
|
419
|
-
marker
|
|
592
|
+
disposeAndReplace(marker, clientTree);
|
|
420
593
|
break;
|
|
421
594
|
}
|
|
422
595
|
}
|
|
@@ -472,7 +645,7 @@ function suspenseSwapScript(id, nonce) {
|
|
|
472
645
|
);
|
|
473
646
|
}
|
|
474
647
|
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
475
|
-
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>`;
|
|
648
|
+
return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${id}");var f=document.querySelector('[data-sibu-suspense-id="${id}"]');if(t&&f){while(f.firstChild)f.removeChild(f.firstChild);while(t.firstChild)f.appendChild(t.firstChild);t.remove();f.removeAttribute("data-sibu-suspense-id");}})()</script>`;
|
|
476
649
|
}
|
|
477
650
|
async function* renderToSuspenseStream(element, pendingBoundaries = [], options) {
|
|
478
651
|
yield* renderToStream(element);
|
|
@@ -522,6 +695,10 @@ function escapeAttr(str) {
|
|
|
522
695
|
}
|
|
523
696
|
|
|
524
697
|
export {
|
|
698
|
+
planMetaEntry,
|
|
699
|
+
planHeadElementEntry,
|
|
700
|
+
STATIC_VALUE_RESOLVER,
|
|
701
|
+
serializeHeadEntry,
|
|
525
702
|
renderToString,
|
|
526
703
|
hydrate,
|
|
527
704
|
trustHTML,
|