@bquery/bquery 1.10.0 → 1.11.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 +44 -19
- package/dist/{a11y-DG2i4iZN.js → a11y-DgUQ8-fI.js} +1 -1
- package/dist/{a11y-DG2i4iZN.js.map → a11y-DgUQ8-fI.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{component-DRotf1hl.js → component-D8ydhe58.js} +2 -2
- package/dist/{component-DRotf1hl.js.map → component-D8ydhe58.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/concurrency-BU1wPEsZ.js.map +1 -1
- package/dist/{constraints-CqjhmpZC.js → constraints-Dlbx_m1b.js} +1 -1
- package/dist/{constraints-CqjhmpZC.js.map → constraints-Dlbx_m1b.js.map} +1 -1
- package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
- package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
- package/dist/core.es.mjs +1 -1
- package/dist/{custom-directives-BjFzFhuf.js → custom-directives-5DlKqvd2.js} +1 -1
- package/dist/{custom-directives-BjFzFhuf.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
- package/dist/{devtools-C5FExMwv.js → devtools-QosAqo0T.js} +2 -2
- package/dist/{devtools-C5FExMwv.js.map → devtools-QosAqo0T.js.map} +1 -1
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-BAqzPlSo.js → dnd-d2OU4len.js} +1 -1
- package/dist/{dnd-BAqzPlSo.js.map → dnd-d2OU4len.js.map} +1 -1
- package/dist/dnd.es.mjs +1 -1
- package/dist/{forms-Dx1Scvh0.js → forms-BLx4ZzT7.js} +1 -1
- package/dist/{forms-Dx1Scvh0.js.map → forms-BLx4ZzT7.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +4 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +258 -219
- package/dist/full.iife.js +41 -37
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +41 -37
- package/dist/full.umd.js.map +1 -1
- package/dist/{i18n-Cazyk9RD.js → i18n--p7PM-9r.js} +1 -1
- package/dist/{i18n-Cazyk9RD.js.map → i18n--p7PM-9r.js.map} +1 -1
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.mjs +291 -252
- package/dist/match-CrZRVC4z.js +174 -0
- package/dist/match-CrZRVC4z.js.map +1 -0
- package/dist/{media-dAKIGPk3.js → media-gjbWNq50.js} +1 -1
- package/dist/{media-dAKIGPk3.js.map → media-gjbWNq50.js.map} +1 -1
- package/dist/media.es.mjs +1 -1
- package/dist/motion-BBMso9Ir.js.map +1 -1
- package/dist/{mount-C8O2vXkQ.js → mount-0A9qtcRJ.js} +3 -3
- package/dist/{mount-C8O2vXkQ.js.map → mount-0A9qtcRJ.js.map} +1 -1
- package/dist/platform-BPHIXbw8.js.map +1 -1
- package/dist/{plugin-DjTqWg-P.js → plugin-SZEirbwq.js} +2 -2
- package/dist/{plugin-DjTqWg-P.js.map → plugin-SZEirbwq.js.map} +1 -1
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive-BAd2hfl8.js.map +1 -1
- package/dist/{registry-Cr6VH8CR.js → registry-jpUQHf4E.js} +1 -1
- package/dist/{registry-Cr6VH8CR.js.map → registry-jpUQHf4E.js.map} +1 -1
- package/dist/router-C4weu0QL.js +333 -0
- package/dist/router-C4weu0QL.js.map +1 -0
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
- package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
- package/dist/security.es.mjs +1 -1
- package/dist/server/create-server.d.ts +25 -0
- package/dist/server/create-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/types.d.ts +396 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server-QdyKtCS1.js +349 -0
- package/dist/server-QdyKtCS1.js.map +1 -0
- package/dist/server.es.mjs +6 -0
- package/dist/ssr/adapters.d.ts +74 -0
- package/dist/ssr/adapters.d.ts.map +1 -0
- package/dist/ssr/async.d.ts +40 -0
- package/dist/ssr/async.d.ts.map +1 -0
- package/dist/ssr/config.d.ts +60 -0
- package/dist/ssr/config.d.ts.map +1 -0
- package/dist/ssr/context.d.ts +73 -0
- package/dist/ssr/context.d.ts.map +1 -0
- package/dist/ssr/defer-brand.d.ts +5 -0
- package/dist/ssr/defer-brand.d.ts.map +1 -0
- package/dist/ssr/escape.d.ts +17 -0
- package/dist/ssr/escape.d.ts.map +1 -0
- package/dist/ssr/expression.d.ts +44 -0
- package/dist/ssr/expression.d.ts.map +1 -0
- package/dist/ssr/hash.d.ts +39 -0
- package/dist/ssr/hash.d.ts.map +1 -0
- package/dist/ssr/head.d.ts +102 -0
- package/dist/ssr/head.d.ts.map +1 -0
- package/dist/ssr/html-parser.d.ts +58 -0
- package/dist/ssr/html-parser.d.ts.map +1 -0
- package/dist/ssr/index.d.ts +49 -43
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/mismatch.d.ts +60 -0
- package/dist/ssr/mismatch.d.ts.map +1 -0
- package/dist/ssr/render-async.d.ts +84 -0
- package/dist/ssr/render-async.d.ts.map +1 -0
- package/dist/ssr/render.d.ts.map +1 -1
- package/dist/ssr/renderer.d.ts +25 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/resumability.d.ts +65 -0
- package/dist/ssr/resumability.d.ts.map +1 -0
- package/dist/ssr/router-bridge.d.ts +101 -0
- package/dist/ssr/router-bridge.d.ts.map +1 -0
- package/dist/ssr/runtime.d.ts +63 -0
- package/dist/ssr/runtime.d.ts.map +1 -0
- package/dist/ssr/serialize.d.ts.map +1 -1
- package/dist/ssr/store-snapshot.d.ts +87 -0
- package/dist/ssr/store-snapshot.d.ts.map +1 -0
- package/dist/ssr/strategies.d.ts +43 -0
- package/dist/ssr/strategies.d.ts.map +1 -0
- package/dist/ssr/suspense.d.ts +47 -0
- package/dist/ssr/suspense.d.ts.map +1 -0
- package/dist/ssr/types.d.ts +17 -0
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr-Bt6BQA3J.js +2127 -0
- package/dist/ssr-Bt6BQA3J.js.map +1 -0
- package/dist/ssr.es.mjs +42 -7
- package/dist/{store-CjmEeX9-.js → store-DnXuu6Li.js} +2 -2
- package/dist/{store-CjmEeX9-.js.map → store-DnXuu6Li.js.map} +1 -1
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs +1 -1
- package/dist/{testing-TdfaL7VE.js → testing-CeMUwrRD.js} +2 -2
- package/dist/{testing-TdfaL7VE.js.map → testing-CeMUwrRD.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/view.es.mjs +1 -1
- package/package.json +17 -12
- package/src/full.ts +99 -0
- package/src/index.ts +3 -0
- package/src/server/create-server.ts +754 -0
- package/src/server/index.ts +33 -0
- package/src/server/types.ts +490 -0
- package/src/ssr/adapters.ts +330 -0
- package/src/ssr/async.ts +125 -0
- package/src/ssr/config.ts +86 -0
- package/src/ssr/context.ts +245 -0
- package/src/ssr/defer-brand.ts +3 -0
- package/src/ssr/escape.ts +25 -0
- package/src/ssr/expression.ts +669 -0
- package/src/ssr/hash.ts +71 -0
- package/src/ssr/head.ts +240 -0
- package/src/ssr/html-parser.ts +387 -0
- package/src/ssr/index.ts +136 -43
- package/src/ssr/mismatch.ts +110 -0
- package/src/ssr/render-async.ts +286 -0
- package/src/ssr/render.ts +130 -59
- package/src/ssr/renderer.ts +453 -0
- package/src/ssr/resumability.ts +142 -0
- package/src/ssr/router-bridge.ts +177 -0
- package/src/ssr/runtime.ts +131 -0
- package/src/ssr/serialize.ts +1 -27
- package/src/ssr/store-snapshot.ts +209 -0
- package/src/ssr/strategies.ts +245 -0
- package/src/ssr/suspense.ts +504 -0
- package/src/ssr/types.ts +18 -0
- package/dist/router-CCepRMpC.js +0 -493
- package/dist/router-CCepRMpC.js.map +0 -1
- package/dist/ssr-D-1IPcfw.js +0 -248
- package/dist/ssr-D-1IPcfw.js.map +0 -1
package/src/ssr/render.ts
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
import { isComputed, isSignal, type Signal } from '../reactive/index';
|
|
12
12
|
import { DANGEROUS_PROTOCOLS } from '../security/constants';
|
|
13
|
-
import { sanitizeHtml } from '../security/sanitize';
|
|
14
13
|
import type { BindingContext } from '../view/types';
|
|
14
|
+
import { getDOMParserImpl, resolveBackend } from './config';
|
|
15
|
+
import { cheapHash, collectDirectiveSignatureFromElement, HYDRATION_HASH_ATTR } from './hash';
|
|
16
|
+
import { renderTemplatePure, sanitizeHtmlForSSR } from './renderer';
|
|
15
17
|
import type { RenderOptions, SSRResult } from './types';
|
|
16
18
|
import { serializeStoreState } from './serialize';
|
|
17
19
|
|
|
@@ -32,6 +34,9 @@ const VOID_ELEMENTS = new Set([
|
|
|
32
34
|
'wbr',
|
|
33
35
|
]);
|
|
34
36
|
|
|
37
|
+
const TEXT_NODE_TYPE = 3;
|
|
38
|
+
const ELEMENT_NODE_TYPE = 1;
|
|
39
|
+
|
|
35
40
|
const escapeHtmlText = (value: string): string =>
|
|
36
41
|
value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
37
42
|
|
|
@@ -68,11 +73,11 @@ const isUnsafeUrlValue = (value: string): boolean => {
|
|
|
68
73
|
};
|
|
69
74
|
|
|
70
75
|
const serializeSSRNode = (node: Node): string => {
|
|
71
|
-
if (node.nodeType ===
|
|
76
|
+
if (node.nodeType === TEXT_NODE_TYPE) {
|
|
72
77
|
return escapeHtmlText(node.textContent ?? '');
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
if (node.nodeType !==
|
|
80
|
+
if (node.nodeType !== ELEMENT_NODE_TYPE) {
|
|
76
81
|
return '';
|
|
77
82
|
}
|
|
78
83
|
|
|
@@ -207,8 +212,71 @@ const processSSRElement = (
|
|
|
207
212
|
el: Element,
|
|
208
213
|
context: BindingContext,
|
|
209
214
|
prefix: string,
|
|
210
|
-
|
|
215
|
+
annotateHydration = false
|
|
211
216
|
): boolean => {
|
|
217
|
+
// Handle bq-for before other directives so each clone gets an item-scoped context.
|
|
218
|
+
const forExpr = el.getAttribute(`${prefix}-for`);
|
|
219
|
+
const parsedFor = forExpr !== null ? parseForExpression(forExpr) : null;
|
|
220
|
+
if (forExpr !== null && !parsedFor) {
|
|
221
|
+
// Remove invalid directives before signature capture so hydration hashes
|
|
222
|
+
// match the DOM-free renderer's normalized output.
|
|
223
|
+
el.removeAttribute(`${prefix}-for`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Capture directive signature after normalizing invalid directives, but
|
|
227
|
+
// before mutating any still-effective directive attributes.
|
|
228
|
+
const signature = annotateHydration ? collectDirectiveSignatureFromElement(el, prefix) : '';
|
|
229
|
+
|
|
230
|
+
if (forExpr !== null) {
|
|
231
|
+
if (parsedFor) {
|
|
232
|
+
const list = evaluateSSR<unknown[]>(parsedFor.listExpr, context);
|
|
233
|
+
if (el.parentNode) {
|
|
234
|
+
const parent = el.parentNode;
|
|
235
|
+
if (!Array.isArray(list)) {
|
|
236
|
+
parent.removeChild(el);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < list.length; i++) {
|
|
241
|
+
const item = list[i];
|
|
242
|
+
const clone = el.cloneNode(true) as Element;
|
|
243
|
+
|
|
244
|
+
// Remove the bq-for attribute from clones
|
|
245
|
+
clone.removeAttribute(`${prefix}-for`);
|
|
246
|
+
clone.removeAttribute(':key');
|
|
247
|
+
clone.removeAttribute(`${prefix}-key`);
|
|
248
|
+
|
|
249
|
+
// Create item context
|
|
250
|
+
const itemContext: BindingContext = {
|
|
251
|
+
...context,
|
|
252
|
+
[parsedFor.itemName]: item,
|
|
253
|
+
};
|
|
254
|
+
if (parsedFor.indexName) {
|
|
255
|
+
itemContext[parsedFor.indexName] = i;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Recursively process the clone
|
|
259
|
+
const shouldRenderClone = processSSRElement(
|
|
260
|
+
clone,
|
|
261
|
+
itemContext,
|
|
262
|
+
prefix,
|
|
263
|
+
annotateHydration
|
|
264
|
+
);
|
|
265
|
+
if (!shouldRenderClone) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
processSSRChildren(clone, itemContext, prefix, annotateHydration);
|
|
269
|
+
|
|
270
|
+
parent.insertBefore(clone, el);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Remove the original template element
|
|
274
|
+
parent.removeChild(el);
|
|
275
|
+
return true; // Already handled children
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
212
280
|
// Handle bq-if: remove element if condition is falsy
|
|
213
281
|
const ifExpr = el.getAttribute(`${prefix}-if`);
|
|
214
282
|
if (ifExpr !== null) {
|
|
@@ -243,7 +311,7 @@ const processSSRElement = (
|
|
|
243
311
|
const htmlExpr = el.getAttribute(`${prefix}-html`);
|
|
244
312
|
if (htmlExpr !== null) {
|
|
245
313
|
const value = evaluateSSR(htmlExpr, context);
|
|
246
|
-
el.innerHTML =
|
|
314
|
+
el.innerHTML = sanitizeHtmlForSSR(String(value ?? ''));
|
|
247
315
|
}
|
|
248
316
|
|
|
249
317
|
// Handle bq-class: add classes
|
|
@@ -311,44 +379,8 @@ const processSSRElement = (
|
|
|
311
379
|
}
|
|
312
380
|
}
|
|
313
381
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (forExpr !== null) {
|
|
317
|
-
const parsed = parseForExpression(forExpr);
|
|
318
|
-
if (parsed) {
|
|
319
|
-
const list = evaluateSSR<unknown[]>(parsed.listExpr, context);
|
|
320
|
-
if (Array.isArray(list) && el.parentNode) {
|
|
321
|
-
const parent = el.parentNode;
|
|
322
|
-
for (let i = 0; i < list.length; i++) {
|
|
323
|
-
const item = list[i];
|
|
324
|
-
const clone = el.cloneNode(true) as Element;
|
|
325
|
-
|
|
326
|
-
// Remove the bq-for attribute from clones
|
|
327
|
-
clone.removeAttribute(`${prefix}-for`);
|
|
328
|
-
clone.removeAttribute(':key');
|
|
329
|
-
clone.removeAttribute(`${prefix}-key`);
|
|
330
|
-
|
|
331
|
-
// Create item context
|
|
332
|
-
const itemContext: BindingContext = {
|
|
333
|
-
...context,
|
|
334
|
-
[parsed.itemName]: item,
|
|
335
|
-
};
|
|
336
|
-
if (parsed.indexName) {
|
|
337
|
-
itemContext[parsed.indexName] = i;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Recursively process the clone
|
|
341
|
-
processSSRElement(clone, itemContext, prefix, doc);
|
|
342
|
-
processSSRChildren(clone, itemContext, prefix, doc);
|
|
343
|
-
|
|
344
|
-
parent.insertBefore(clone, el);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Remove the original template element
|
|
348
|
-
parent.removeChild(el);
|
|
349
|
-
return true; // Already handled children
|
|
350
|
-
}
|
|
351
|
-
}
|
|
382
|
+
if (signature) {
|
|
383
|
+
el.setAttribute(HYDRATION_HASH_ATTR, cheapHash(signature));
|
|
352
384
|
}
|
|
353
385
|
|
|
354
386
|
return true;
|
|
@@ -362,29 +394,40 @@ const processSSRChildren = (
|
|
|
362
394
|
parent: Element,
|
|
363
395
|
context: BindingContext,
|
|
364
396
|
prefix: string,
|
|
365
|
-
|
|
397
|
+
annotateHydration = false
|
|
366
398
|
): void => {
|
|
367
|
-
// Process
|
|
399
|
+
// Process a snapshotted child list so removals do not affect iteration
|
|
368
400
|
const children = Array.from(parent.children);
|
|
369
401
|
for (const child of children) {
|
|
370
|
-
|
|
402
|
+
let processedForDirective = false;
|
|
403
|
+
|
|
404
|
+
// Handle elements that start with bq-for before the normal per-element pass.
|
|
371
405
|
if (child.hasAttribute(`${prefix}-for`)) {
|
|
372
|
-
|
|
373
|
-
|
|
406
|
+
const keep = processSSRElement(child, context, prefix, annotateHydration);
|
|
407
|
+
processedForDirective = true;
|
|
374
408
|
if (!keep) {
|
|
375
409
|
child.remove();
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Valid bq-for handling removes/replaces the original template node. If the
|
|
414
|
+
// original child is no longer attached here, recursion has already been
|
|
415
|
+
// handled by the bq-for expansion path.
|
|
416
|
+
if (child.parentNode !== parent) {
|
|
417
|
+
continue;
|
|
376
418
|
}
|
|
377
|
-
continue;
|
|
378
419
|
}
|
|
379
420
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
421
|
+
if (!processedForDirective) {
|
|
422
|
+
const keep = processSSRElement(child, context, prefix, annotateHydration);
|
|
423
|
+
if (!keep) {
|
|
424
|
+
child.remove();
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
384
427
|
}
|
|
385
428
|
|
|
386
429
|
// Recurse into children
|
|
387
|
-
processSSRChildren(child, context, prefix,
|
|
430
|
+
processSSRChildren(child, context, prefix, annotateHydration);
|
|
388
431
|
}
|
|
389
432
|
};
|
|
390
433
|
|
|
@@ -461,21 +504,49 @@ export const renderToString = (
|
|
|
461
504
|
data: BindingContext,
|
|
462
505
|
options: RenderOptions = {}
|
|
463
506
|
): SSRResult => {
|
|
464
|
-
const {
|
|
507
|
+
const {
|
|
508
|
+
prefix = 'bq',
|
|
509
|
+
stripDirectives = false,
|
|
510
|
+
includeStoreState = false,
|
|
511
|
+
annotateHydration = false,
|
|
512
|
+
} = options;
|
|
465
513
|
|
|
466
514
|
if (!template || typeof template !== 'string') {
|
|
467
515
|
throw new Error('bQuery SSR: template must be a non-empty string.');
|
|
468
516
|
}
|
|
469
517
|
|
|
470
|
-
|
|
518
|
+
const normalizedTemplate = template.trim();
|
|
519
|
+
|
|
520
|
+
// Resolve the renderer backend. Defaults to the legacy DOM-based path when
|
|
521
|
+
// a `DOMParser` is available (browser/happy-dom in tests); otherwise the
|
|
522
|
+
// pure DOM-free renderer kicks in automatically — this is what makes
|
|
523
|
+
// `renderToString()` work seamlessly on Bun, Deno and Node ≥ 24.
|
|
524
|
+
const backend = resolveBackend();
|
|
525
|
+
|
|
526
|
+
if (backend === 'pure') {
|
|
527
|
+
const html = renderTemplatePure(normalizedTemplate, data, {
|
|
528
|
+
prefix,
|
|
529
|
+
stripDirectives,
|
|
530
|
+
annotateHydration,
|
|
531
|
+
});
|
|
532
|
+
let storeState: string | undefined;
|
|
533
|
+
if (includeStoreState) {
|
|
534
|
+
const storeIds = Array.isArray(includeStoreState) ? includeStoreState : undefined;
|
|
535
|
+
storeState = serializeStoreState({ storeIds }).stateJson;
|
|
536
|
+
}
|
|
537
|
+
return { html, storeState };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const DOMParserImpl = getDOMParserImpl();
|
|
541
|
+
if (!DOMParserImpl) {
|
|
471
542
|
throw new Error(
|
|
472
543
|
'bQuery SSR: DOMParser is not available in this environment. Provide a DOMParser-compatible implementation before calling renderToString().'
|
|
473
544
|
);
|
|
474
545
|
}
|
|
475
546
|
|
|
476
547
|
// Create a DOM document for processing
|
|
477
|
-
const parser = new
|
|
478
|
-
const doc = parser.parseFromString(
|
|
548
|
+
const parser = new DOMParserImpl();
|
|
549
|
+
const doc = parser.parseFromString(normalizedTemplate, 'text/html');
|
|
479
550
|
const body = doc.body || doc.documentElement;
|
|
480
551
|
|
|
481
552
|
if (!body) {
|
|
@@ -483,7 +554,7 @@ export const renderToString = (
|
|
|
483
554
|
}
|
|
484
555
|
|
|
485
556
|
// Process all children of the body
|
|
486
|
-
processSSRChildren(body, data, prefix,
|
|
557
|
+
processSSRChildren(body, data, prefix, annotateHydration);
|
|
487
558
|
|
|
488
559
|
// Strip directive attributes if requested
|
|
489
560
|
if (stripDirectives) {
|