@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.
Files changed (155) hide show
  1. package/README.md +44 -19
  2. package/dist/{a11y-DG2i4iZN.js → a11y-DgUQ8-fI.js} +1 -1
  3. package/dist/{a11y-DG2i4iZN.js.map → a11y-DgUQ8-fI.js.map} +1 -1
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/{component-DRotf1hl.js → component-D8ydhe58.js} +2 -2
  6. package/dist/{component-DRotf1hl.js.map → component-D8ydhe58.js.map} +1 -1
  7. package/dist/component.es.mjs +1 -1
  8. package/dist/concurrency-BU1wPEsZ.js.map +1 -1
  9. package/dist/{constraints-CqjhmpZC.js → constraints-Dlbx_m1b.js} +1 -1
  10. package/dist/{constraints-CqjhmpZC.js.map → constraints-Dlbx_m1b.js.map} +1 -1
  11. package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
  12. package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
  13. package/dist/core.es.mjs +1 -1
  14. package/dist/{custom-directives-BjFzFhuf.js → custom-directives-5DlKqvd2.js} +1 -1
  15. package/dist/{custom-directives-BjFzFhuf.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
  16. package/dist/{devtools-C5FExMwv.js → devtools-QosAqo0T.js} +2 -2
  17. package/dist/{devtools-C5FExMwv.js.map → devtools-QosAqo0T.js.map} +1 -1
  18. package/dist/devtools.es.mjs +1 -1
  19. package/dist/{dnd-BAqzPlSo.js → dnd-d2OU4len.js} +1 -1
  20. package/dist/{dnd-BAqzPlSo.js.map → dnd-d2OU4len.js.map} +1 -1
  21. package/dist/dnd.es.mjs +1 -1
  22. package/dist/{forms-Dx1Scvh0.js → forms-BLx4ZzT7.js} +1 -1
  23. package/dist/{forms-Dx1Scvh0.js.map → forms-BLx4ZzT7.js.map} +1 -1
  24. package/dist/forms.es.mjs +1 -1
  25. package/dist/full.d.ts +4 -2
  26. package/dist/full.d.ts.map +1 -1
  27. package/dist/full.es.mjs +258 -219
  28. package/dist/full.iife.js +41 -37
  29. package/dist/full.iife.js.map +1 -1
  30. package/dist/full.umd.js +41 -37
  31. package/dist/full.umd.js.map +1 -1
  32. package/dist/{i18n-Cazyk9RD.js → i18n--p7PM-9r.js} +1 -1
  33. package/dist/{i18n-Cazyk9RD.js.map → i18n--p7PM-9r.js.map} +1 -1
  34. package/dist/i18n.es.mjs +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.es.mjs +291 -252
  38. package/dist/match-CrZRVC4z.js +174 -0
  39. package/dist/match-CrZRVC4z.js.map +1 -0
  40. package/dist/{media-dAKIGPk3.js → media-gjbWNq50.js} +1 -1
  41. package/dist/{media-dAKIGPk3.js.map → media-gjbWNq50.js.map} +1 -1
  42. package/dist/media.es.mjs +1 -1
  43. package/dist/motion-BBMso9Ir.js.map +1 -1
  44. package/dist/{mount-C8O2vXkQ.js → mount-0A9qtcRJ.js} +3 -3
  45. package/dist/{mount-C8O2vXkQ.js.map → mount-0A9qtcRJ.js.map} +1 -1
  46. package/dist/platform-BPHIXbw8.js.map +1 -1
  47. package/dist/{plugin-DjTqWg-P.js → plugin-SZEirbwq.js} +2 -2
  48. package/dist/{plugin-DjTqWg-P.js.map → plugin-SZEirbwq.js.map} +1 -1
  49. package/dist/plugin.es.mjs +1 -1
  50. package/dist/reactive-BAd2hfl8.js.map +1 -1
  51. package/dist/{registry-Cr6VH8CR.js → registry-jpUQHf4E.js} +1 -1
  52. package/dist/{registry-Cr6VH8CR.js.map → registry-jpUQHf4E.js.map} +1 -1
  53. package/dist/router-C4weu0QL.js +333 -0
  54. package/dist/router-C4weu0QL.js.map +1 -0
  55. package/dist/router.es.mjs +1 -1
  56. package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
  57. package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
  58. package/dist/security.es.mjs +1 -1
  59. package/dist/server/create-server.d.ts +25 -0
  60. package/dist/server/create-server.d.ts.map +1 -0
  61. package/dist/server/index.d.ts +11 -0
  62. package/dist/server/index.d.ts.map +1 -0
  63. package/dist/server/types.d.ts +396 -0
  64. package/dist/server/types.d.ts.map +1 -0
  65. package/dist/server-QdyKtCS1.js +349 -0
  66. package/dist/server-QdyKtCS1.js.map +1 -0
  67. package/dist/server.es.mjs +6 -0
  68. package/dist/ssr/adapters.d.ts +74 -0
  69. package/dist/ssr/adapters.d.ts.map +1 -0
  70. package/dist/ssr/async.d.ts +40 -0
  71. package/dist/ssr/async.d.ts.map +1 -0
  72. package/dist/ssr/config.d.ts +60 -0
  73. package/dist/ssr/config.d.ts.map +1 -0
  74. package/dist/ssr/context.d.ts +73 -0
  75. package/dist/ssr/context.d.ts.map +1 -0
  76. package/dist/ssr/defer-brand.d.ts +5 -0
  77. package/dist/ssr/defer-brand.d.ts.map +1 -0
  78. package/dist/ssr/escape.d.ts +17 -0
  79. package/dist/ssr/escape.d.ts.map +1 -0
  80. package/dist/ssr/expression.d.ts +44 -0
  81. package/dist/ssr/expression.d.ts.map +1 -0
  82. package/dist/ssr/hash.d.ts +39 -0
  83. package/dist/ssr/hash.d.ts.map +1 -0
  84. package/dist/ssr/head.d.ts +102 -0
  85. package/dist/ssr/head.d.ts.map +1 -0
  86. package/dist/ssr/html-parser.d.ts +58 -0
  87. package/dist/ssr/html-parser.d.ts.map +1 -0
  88. package/dist/ssr/index.d.ts +49 -43
  89. package/dist/ssr/index.d.ts.map +1 -1
  90. package/dist/ssr/mismatch.d.ts +60 -0
  91. package/dist/ssr/mismatch.d.ts.map +1 -0
  92. package/dist/ssr/render-async.d.ts +84 -0
  93. package/dist/ssr/render-async.d.ts.map +1 -0
  94. package/dist/ssr/render.d.ts.map +1 -1
  95. package/dist/ssr/renderer.d.ts +25 -0
  96. package/dist/ssr/renderer.d.ts.map +1 -0
  97. package/dist/ssr/resumability.d.ts +65 -0
  98. package/dist/ssr/resumability.d.ts.map +1 -0
  99. package/dist/ssr/router-bridge.d.ts +101 -0
  100. package/dist/ssr/router-bridge.d.ts.map +1 -0
  101. package/dist/ssr/runtime.d.ts +63 -0
  102. package/dist/ssr/runtime.d.ts.map +1 -0
  103. package/dist/ssr/serialize.d.ts.map +1 -1
  104. package/dist/ssr/store-snapshot.d.ts +87 -0
  105. package/dist/ssr/store-snapshot.d.ts.map +1 -0
  106. package/dist/ssr/strategies.d.ts +43 -0
  107. package/dist/ssr/strategies.d.ts.map +1 -0
  108. package/dist/ssr/suspense.d.ts +47 -0
  109. package/dist/ssr/suspense.d.ts.map +1 -0
  110. package/dist/ssr/types.d.ts +17 -0
  111. package/dist/ssr/types.d.ts.map +1 -1
  112. package/dist/ssr-Bt6BQA3J.js +2127 -0
  113. package/dist/ssr-Bt6BQA3J.js.map +1 -0
  114. package/dist/ssr.es.mjs +42 -7
  115. package/dist/{store-CjmEeX9-.js → store-DnXuu6Li.js} +2 -2
  116. package/dist/{store-CjmEeX9-.js.map → store-DnXuu6Li.js.map} +1 -1
  117. package/dist/store.es.mjs +2 -2
  118. package/dist/storybook.es.mjs +1 -1
  119. package/dist/{testing-TdfaL7VE.js → testing-CeMUwrRD.js} +2 -2
  120. package/dist/{testing-TdfaL7VE.js.map → testing-CeMUwrRD.js.map} +1 -1
  121. package/dist/testing.es.mjs +1 -1
  122. package/dist/view.es.mjs +1 -1
  123. package/package.json +17 -12
  124. package/src/full.ts +99 -0
  125. package/src/index.ts +3 -0
  126. package/src/server/create-server.ts +754 -0
  127. package/src/server/index.ts +33 -0
  128. package/src/server/types.ts +490 -0
  129. package/src/ssr/adapters.ts +330 -0
  130. package/src/ssr/async.ts +125 -0
  131. package/src/ssr/config.ts +86 -0
  132. package/src/ssr/context.ts +245 -0
  133. package/src/ssr/defer-brand.ts +3 -0
  134. package/src/ssr/escape.ts +25 -0
  135. package/src/ssr/expression.ts +669 -0
  136. package/src/ssr/hash.ts +71 -0
  137. package/src/ssr/head.ts +240 -0
  138. package/src/ssr/html-parser.ts +387 -0
  139. package/src/ssr/index.ts +136 -43
  140. package/src/ssr/mismatch.ts +110 -0
  141. package/src/ssr/render-async.ts +286 -0
  142. package/src/ssr/render.ts +130 -59
  143. package/src/ssr/renderer.ts +453 -0
  144. package/src/ssr/resumability.ts +142 -0
  145. package/src/ssr/router-bridge.ts +177 -0
  146. package/src/ssr/runtime.ts +131 -0
  147. package/src/ssr/serialize.ts +1 -27
  148. package/src/ssr/store-snapshot.ts +209 -0
  149. package/src/ssr/strategies.ts +245 -0
  150. package/src/ssr/suspense.ts +504 -0
  151. package/src/ssr/types.ts +18 -0
  152. package/dist/router-CCepRMpC.js +0 -493
  153. package/dist/router-CCepRMpC.js.map +0 -1
  154. package/dist/ssr-D-1IPcfw.js +0 -248
  155. 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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 === Node.TEXT_NODE) {
76
+ if (node.nodeType === TEXT_NODE_TYPE) {
72
77
  return escapeHtmlText(node.textContent ?? '');
73
78
  }
74
79
 
75
- if (node.nodeType !== Node.ELEMENT_NODE) {
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
- doc: Document
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 = String(sanitizeHtml(String(value ?? '')));
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
- // Handle bq-for: list rendering
315
- const forExpr = el.getAttribute(`${prefix}-for`);
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
- doc: Document
397
+ annotateHydration = false
366
398
  ): void => {
367
- // Process children in reverse to handle removals safely
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
- // Skip bq-for elements — they're handled by parent
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
- // Process the for directive on this element
373
- const keep = processSSRElement(child, context, prefix, doc);
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
- const keep = processSSRElement(child, context, prefix, doc);
381
- if (!keep) {
382
- child.remove();
383
- continue;
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, doc);
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 { prefix = 'bq', stripDirectives = false, includeStoreState = false } = options;
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
- if (typeof DOMParser === 'undefined') {
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 DOMParser();
478
- const doc = parser.parseFromString(template.trim(), 'text/html');
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, doc);
557
+ processSSRChildren(body, data, prefix, annotateHydration);
487
558
 
488
559
  // Strip directive attributes if requested
489
560
  if (stripDirectives) {