@vesk/compiler 0.2.24 → 0.2.26

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAwCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAw2CD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAqED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,MAAM,CAOnN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBxJ;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAyCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAk8CD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAqED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,MAAM,CAOnN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBxJ;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -9,6 +9,7 @@ import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
9
9
  import { extractRuntimeNames, extractTopLevelNames } from '@vesk/compiler/src/server-utils';
10
10
  import { importBindingPairs, localValueImportNames } from '@vesk/compiler/src/module-imports';
11
11
  import { stripTrackGeneric } from '@vesk/compiler/src/scan';
12
+ import { importModuleTarget } from '@vesk/compiler/src/tokens';
12
13
  import { inlineMdImportsFrom } from '@vesk/compiler/src/md-inline';
13
14
  import { stripTsTypes, hasTsSyntax } from '@vesk/compiler/src/strip-ts';
14
15
  function memberExpr(object, property) {
@@ -253,6 +254,11 @@ class Ctx {
253
254
  c = 0;
254
255
  importedNames = new Set();
255
256
  linkNames = new Set();
257
+ // Runtime component imports that claim their own SSR root via `nextElement`
258
+ // (Link/NavLink/Md/Form/Field/LoadingIndicator) and return a DocumentFragment
259
+ // when claimed. They need no call-site adoption guard — the guard targets
260
+ // genuinely-plain components that never touch the walker.
261
+ selfClaimNames = new Set();
256
262
  delegatedEvents = new Set();
257
263
  directEvents = new Set();
258
264
  hydrate = false;
@@ -412,16 +418,33 @@ function emitStatic(ctx, node, tracked, effectsVar) {
412
418
  ctx.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml(attr.value))});`);
413
419
  }
414
420
  }
421
+ let residueBefore = 0;
415
422
  for (const child of children) {
416
423
  const childVar = emitNode(ctx, child, tracked, effectsVar, el);
417
424
  if (childVar) {
418
425
  if (ctx.hydrate) {
419
- ctx.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
426
+ // Fresh text nodes (static or dynamic) cannot be appended at the end of
427
+ // a claimed element: its SSR element children (component-boundary
428
+ // wrappers, skipped static markup) are still in the DOM at their SSR
429
+ // slots, so appending would move the text AFTER them, reordering
430
+ // mixed text + component children (the hero "compiler-first framework ·
431
+ // <VersionBadge />" label). Insert the fresh node at the SSR position
432
+ // of the text instead: the k-th source text child precedes the residue
433
+ // elements rendered before it, so it slots in before
434
+ // `el.__vsk_ssrEls[k]` (the residue list captured by the runtime at
435
+ // claim time).
436
+ if (child instanceof TextNode || child instanceof DynamicBinding) {
437
+ ctx.push(`if (${childVar}.parentNode !== ${el}) { const __ssr = ${el}.__vsk_ssrEls || []; if (${residueBefore} < __ssr.length) ${el}.insertBefore(${childVar}, __ssr[${residueBefore}]); else ${el}.appendChild(${childVar}); }`);
438
+ }
439
+ else {
440
+ ctx.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
441
+ }
420
442
  }
421
443
  else {
422
444
  ctx.push(`${el}.appendChild(${childVar});`);
423
445
  }
424
446
  }
447
+ residueBefore += ssrResidueEstimate(child);
425
448
  }
426
449
  for (const attr of dynAttrs) {
427
450
  const target = attr.target || '';
@@ -640,7 +663,11 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
640
663
  }
641
664
  const propsObj = `{ ${propsEntries.join(', ')} }`;
642
665
  const v = ctx.n();
643
- const awaitKw = ctx.asyncComps.has(node.componentName) ? 'await ' : '';
666
+ // Await children whenever the calling scope is async. Async-ness is
667
+ // parent-driven (mirroring the server side) so that imported async
668
+ // components from other files — whose names are not in the local
669
+ // asyncComps set — still resolve to nodes before append/insert.
670
+ const awaitKw = ctx.isAsyncScope ? 'await ' : '';
644
671
  // Member-expression tags (`<it.icon>`) carry the raw component-valued
645
672
  // expression — invoke it directly; it is never a registry name.
646
673
  const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
@@ -649,15 +676,30 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
649
676
  ?? (ctx.importedNames.has(node.componentName)
650
677
  ? node.componentName
651
678
  : `__components[${JSON.stringify(node.componentName)}]`);
652
- const subScope = () => ctx.inTryBody
653
- ? `${ctx.walker}.subWalker(${parentVar})`
654
- : `${ctx.walker}.subWalker(${ctx.walker}.nextElement())`;
679
+ // SSR for component calls is marker-ONLY (`<!--vsk-->` + the component's
680
+ // own root; no display:contents wrapper). Compiled `.vsk` components claim
681
+ // their own root on the SHARED walker. Call targets that are not known
682
+ // compiled components (imported values, member-expression tags) cannot
683
+ // claim, so after the call we adopt their SSR root ourselves and replace it
684
+ // with the fresh node they returned — never a duplicate, never a box.
685
+ const plainTarget = !!(calleeExpr || ctx.importedNames.has(node.componentName)) && !ctx.selfClaimNames.has(node.componentName);
686
+ const walkerArg = ctx.walker;
687
+ const maybeReplace = (v) => {
688
+ if (!plainTarget)
689
+ return;
690
+ // nodeType 11 = DocumentFragment: self-claiming imports (Link/Md/Form)
691
+ // return a fragment when they adopted their SSR root, so there is nothing
692
+ // to replace — claiming here would steal the NEXT sibling's root.
693
+ ctx.push(`if (${v} && ${v}.parentNode == null && ${v}.nodeType !== 11) { const __sr = ${walkerArg}.claimOnly(); if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v}, __sr); }`);
694
+ };
695
+ const maybeRetire = () => {
696
+ if (ctx.selfClaimNames.has(node.componentName)) {
697
+ ctx.push(`${walkerArg}.retireDetached();`);
698
+ }
699
+ };
655
700
  if (node.children.length > 0) {
656
- const walkerVar = ctx.n();
657
- ctx.push(`const ${walkerVar} = ${subScope()};`);
658
701
  const frag = ctx.n();
659
702
  ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
660
- const savedWalker = ctx.walker;
661
703
  const savedEffects = ctx.effects;
662
704
  // Link/NavLink hydrate branches WIPE their SSR anchor (a.replaceChildren())
663
705
  // and remount the children fragment. Suppressed fully-static children would
@@ -666,7 +708,6 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
666
708
  const savedHydrate = ctx.hydrate;
667
709
  if (wipeMount)
668
710
  ctx.hydrate = false;
669
- ctx.walker = walkerVar;
670
711
  ctx.effects = [];
671
712
  for (const child of node.children) {
672
713
  const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
@@ -676,14 +717,17 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
676
717
  for (const eff of ctx.effects)
677
718
  ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
678
719
  ctx.effects = savedEffects;
679
- ctx.walker = savedWalker;
680
720
  ctx.hydrate = savedHydrate;
681
721
  ctx.push(`return $f; })();`);
682
722
  propsEntries.push(`children: ${frag}`);
683
- ctx.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(', ')} }, __registry, ${walkerVar});`);
723
+ ctx.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(', ')} }, __registry, ${walkerArg});`);
724
+ maybeReplace(v);
725
+ maybeRetire();
684
726
  return v;
685
727
  }
686
- ctx.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${subScope()});`);
728
+ ctx.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${walkerArg});`);
729
+ maybeReplace(v);
730
+ maybeRetire();
687
731
  return v;
688
732
  }
689
733
  else {
@@ -1567,11 +1611,12 @@ ${renderAllItems(' ', null)}
1567
1611
  function computeAsyncComponents(comps) {
1568
1612
  return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
1569
1613
  }
1570
- function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set(), linkNames = new Set()) {
1614
+ function generateComponent(comp, importedNames = new Set(), hydrate = false, asyncComps = new Set(), linkNames = new Set(), selfClaimNames = new Set()) {
1571
1615
  const tracked = collectTrackedNames(comp.body);
1572
1616
  const ctx = new Ctx();
1573
1617
  ctx.importedNames = importedNames;
1574
1618
  ctx.linkNames = linkNames;
1619
+ ctx.selfClaimNames = selfClaimNames;
1575
1620
  ctx.hydrate = hydrate;
1576
1621
  ctx.asyncComps = asyncComps;
1577
1622
  ctx.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
@@ -1596,11 +1641,19 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
1596
1641
  const paramInit = buildParamInit(comp.paramNames);
1597
1642
  if (paramInit)
1598
1643
  ctx.push(indent(paramInit));
1644
+ // Hoist TrackDecls to top to avoid TDZ when Head/effects reference them before declaration
1645
+ for (const node of comp.body) {
1646
+ if (node instanceof TrackDecl) {
1647
+ emitNode(ctx, node, tracked, null);
1648
+ }
1649
+ }
1599
1650
  for (const node of comp.body) {
1651
+ if (node instanceof TrackDecl)
1652
+ continue;
1600
1653
  const v = emitNode(ctx, node, tracked, null);
1601
1654
  if (v) {
1602
1655
  if (ctx.hydrate) {
1603
- ctx.push(indent(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
1656
+ ctx.push(indent(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !${v}.parentNode.contains(${v})) $root.appendChild(${v}); }`));
1604
1657
  }
1605
1658
  else {
1606
1659
  ctx.push(indent(`$root.appendChild(${v});`));
@@ -1635,7 +1688,19 @@ function buildComponentMap(irRoot, hydrate = false) {
1635
1688
  // hydration, so their children fragments must be rebuilt in full. Track the
1636
1689
  // file-local binding names regardless of aliasing (`const Nav = NavLink`).
1637
1690
  const linkNames = new Set();
1691
+ // Runtime components that self-claim during hydration (adopt their own SSR
1692
+ // root via nextElement and return a fragment when claimed). Excluded from the
1693
+ // call-site adoption guard; the walker cursor passes them but never a
1694
+ // claimOnly() on their behalf.
1695
+ const selfClaimNames = new Set();
1696
+ const SELF_CLAIM_RUNTIME = new Set(['Link', 'NavLink', 'Md', 'Form', 'Field', 'LoadingIndicator']);
1638
1697
  for (const imp of irRoot.imports) {
1698
+ if (importModuleTarget(imp) !== '@vesk/runtime')
1699
+ continue;
1700
+ for (const pair of importBindingPairs(imp)) {
1701
+ if (SELF_CLAIM_RUNTIME.has(pair.imported))
1702
+ selfClaimNames.add(pair.local);
1703
+ }
1639
1704
  for (const pair of importBindingPairs(imp)) {
1640
1705
  if (pair.imported === 'Link' || pair.imported === 'NavLink')
1641
1706
  linkNames.add(pair.local);
@@ -1650,11 +1715,13 @@ function buildComponentMap(irRoot, hydrate = false) {
1650
1715
  const directNames = new Set([...extractRuntimeNames(irRoot.imports), ...localValueImportNames(irRoot.imports), ...topValueNames]);
1651
1716
  for (const comp of irRoot.components) {
1652
1717
  if (hydrate && isStaticComponent(comp)) {
1653
- const stub = `(props, __registry, __hydrate) => { return __hydrate.root; }`;
1718
+ // Static components keep their SSR content untouched: claim the root
1719
+ // WITHOUT stripping its direct text (nothing will re-create it).
1720
+ const stub = `(props, __registry, __hydrate) => { return __hydrate.claimOnly(); }`;
1654
1721
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
1655
1722
  continue;
1656
1723
  }
1657
- const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames);
1724
+ const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
1658
1725
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
1659
1726
  }
1660
1727
  mapLines.push(`function __cleanup(start, end) {`);
@@ -1683,6 +1750,40 @@ function buildComponentMap(irRoot, hydrate = false) {
1683
1750
  mapLines.push(`}`);
1684
1751
  return mapLines.join('\n\n');
1685
1752
  }
1753
+ function ssrResidueEstimate(node) {
1754
+ if (Array.isArray(node)) {
1755
+ let total = 0;
1756
+ for (const n of node)
1757
+ total += ssrResidueEstimate(n);
1758
+ return total;
1759
+ }
1760
+ if (node instanceof StaticNode)
1761
+ return 1;
1762
+ if (node instanceof ComponentCall)
1763
+ return 1;
1764
+ if (node instanceof OpaqueDynamicRegion) {
1765
+ return Math.max(ssrResidueEstimate(node.consequentNodes), ssrResidueEstimate(node.alternateNodes));
1766
+ }
1767
+ if (node instanceof MapRegion) {
1768
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.alternateNodes));
1769
+ }
1770
+ if (node instanceof ForLoop)
1771
+ return ssrResidueEstimate(node.bodyTemplate);
1772
+ if (node instanceof WhileLoop)
1773
+ return ssrResidueEstimate(node.bodyTemplate);
1774
+ if (node instanceof SwitchBlock) {
1775
+ let max = 0;
1776
+ for (const c of node.cases)
1777
+ max = Math.max(max, ssrResidueEstimate(c.body));
1778
+ return max;
1779
+ }
1780
+ if (node instanceof TryCatch) {
1781
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.catchBody));
1782
+ }
1783
+ // TextNode, DynamicBinding, TrackDecl, RuntimeStatement, SlotNode,
1784
+ // ServerBlock, ClientBlock, HeadBlock emit no SSR element residue.
1785
+ return 0;
1786
+ }
1686
1787
  function isStaticIR(body) {
1687
1788
  for (const node of body) {
1688
1789
  if (node instanceof StaticNode) {
@@ -1 +1 @@
1
- {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAoCrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAmDjJ;AAsWD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAmD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4BzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
1
+ {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAoCrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAmDjJ;AAoWD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAmD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4BzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
@@ -19,14 +19,14 @@ import { localValueImportNames } from '@vesk/compiler/src/module-imports';
19
19
  // the client's for every property, not just sticky.
20
20
  //
21
21
  // The wrapper tag must be `<span>`, not `<div>`. The HTML parser implicitly
22
- // closes an open `<p>` on a flow-content start tag like `<div>`, so a `<div>`
23
- // wrapper inside a `<p>` (a component called inside a paragraph, e.g. a `Link`
24
- // in an eyebrow row) is hoisted out of the paragraph, stranding its `<!--vsk-->`
25
- // marker in the now-empty `<p>` → `nextElementSibling` is null → the hydration
26
- // claim misses and every later claim tag-shifts, wiping the SSR content. `<span>`
27
- // is phrasing content (legal inside `<p>`) and never triggers an implicit close,
28
- // while `display: contents` keeps it box-less everywhere a `<div>` would be.
29
- const HYDRATE_COMPONENT_WRAPPER = '<!--vsk--><span style="display:contents">';
22
+ // Marker-ONLY component boundary. The client's shared walker claims a
23
+ // component call-site by reading the component's own root element off this
24
+ // marker — the SSR output of a component call is exactly `<!--vsk-->` followed
25
+ // by the callee's content. A `<span style="display:contents">` box here would
26
+ // sit between the marker and that root, so a `nextElement(tag)` claim (e.g.
27
+ // LoadingIndicator claiming `div`) hits a tag mismatch, never advances the
28
+ // walker cursor, and every later claim miss wipes the SSR content.
29
+ const HYDRATE_COMPONENT_WRAPPER = '<!--vsk-->';
30
30
  export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
31
31
  importedNames = importedNames || __vskImportedNames;
32
32
  if (node instanceof StaticNode)
@@ -448,9 +448,7 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked) {
448
448
  lines.push(`const ${calleeVar} = ${callee};`);
449
449
  const callExpr = `${awaitKw}${calleeVar}(${propsObj}, __registry, (${calleeVar}.__veskScope || __vesk))`;
450
450
  if (__vskHydrate) {
451
- // Wrapper purpose documented at HYDRATE_COMPONENT_WRAPPER; it must never
452
- // become a layout box (see the constant's comment for why).
453
- lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr} || '') + '</span>');`);
451
+ lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr} || ''));`);
454
452
  }
455
453
  else {
456
454
  lines.push(`__out.push(${callExpr} || '');`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "type": "module",
5
5
  "description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@sveltejs/acorn-typescript": "^1.0.0",
23
- "@vesk/types": "^0.2.24",
23
+ "@vesk/types": "^0.2.26",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",