@vesk/vesk-cli 0.2.25 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +267 -61
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -2154,6 +2154,9 @@ function stripDirectTextNodes(el) {
2154
2154
  if (el.childNodes[i].nodeType === 3) el.childNodes[i].remove();
2155
2155
  }
2156
2156
  }
2157
+ function captureSsrElementChildren(el) {
2158
+ el.__vsk_ssrEls = Array.prototype.slice.call(el.children);
2159
+ }
2157
2160
  function assertFullyHydrated(container) {
2158
2161
  let unclaimed = 0;
2159
2162
  let sample = "";
@@ -2198,6 +2201,19 @@ function adoptElement(marker, tag) {
2198
2201
  if (tag && el.tagName.toLowerCase() !== tag) return null;
2199
2202
  marker.remove();
2200
2203
  stripDirectTextNodes(el);
2204
+ captureSsrElementChildren(el);
2205
+ stampClaimed(el);
2206
+ return el;
2207
+ }
2208
+ function adoptElementRaw(marker, tag) {
2209
+ const el = marker.nextElementSibling;
2210
+ if (!el) {
2211
+ marker.remove();
2212
+ return null;
2213
+ }
2214
+ if (tag && el.tagName.toLowerCase() !== tag) return null;
2215
+ marker.remove();
2216
+ captureSsrElementChildren(el);
2201
2217
  stampClaimed(el);
2202
2218
  return el;
2203
2219
  }
@@ -2205,30 +2221,6 @@ function createHydrateWalker(container, markerList) {
2205
2221
  const markers = markerList || (container ? collectVskMarkers(container) : []);
2206
2222
  return new WalkerEngine(container, markers);
2207
2223
  }
2208
- function createHydrateChildWalker(parentEl) {
2209
- let childIdx = 0;
2210
- const children = parentEl ? parentEl.children : [];
2211
- return {
2212
- root: parentEl,
2213
- done() {
2214
- return childIdx >= children.length;
2215
- },
2216
- nextElement(tag) {
2217
- while (childIdx < children.length) {
2218
- const child = children[childIdx++];
2219
- if (!tag || child.tagName.toLowerCase() === tag) {
2220
- stripDirectTextNodes(child);
2221
- stampClaimed(child);
2222
- return child;
2223
- }
2224
- }
2225
- return document.createElement(tag || "div");
2226
- },
2227
- subWalker(rootEl) {
2228
- return createHydrateChildWalker(rootEl);
2229
- }
2230
- };
2231
- }
2232
2224
  function hydrateViewport(container, componentFn, props, rootMargin = 500) {
2233
2225
  if (document.readyState !== "complete") {
2234
2226
  return new Promise((resolve39) => {
@@ -2375,6 +2367,13 @@ var init_hydrate = __esm({
2375
2367
  root;
2376
2368
  markers;
2377
2369
  idx = 0;
2370
+ // Elements already adopted by an earlier claim in this walk. Marker-only SSR
2371
+ // can leave TWO `<!--vsk-->` marks on the same element (the call site and
2372
+ // the callee's own root marker — e.g. Link self-prefixes `<!--vsk-->` and
2373
+ // the shared walker claims its call-site marker), and a claim must never
2374
+ // adopt the same node twice: double-adoption strips its SSR text a second
2375
+ // time and shells the cursor so every later claim misses its element.
2376
+ adopted = /* @__PURE__ */ new WeakSet();
2378
2377
  constructor(root3, markers) {
2379
2378
  this.root = root3;
2380
2379
  this.markers = markers.map((c) => ({ comment: c, state: "unclaimed" }));
@@ -2382,6 +2381,43 @@ var init_hydrate = __esm({
2382
2381
  done() {
2383
2382
  return this.idx >= this.markers.length;
2384
2383
  }
2384
+ // A marker whose `nextElementSibling` was already adopted is a dead alias of
2385
+ // a live claim point (double-marker collision). Retire it and move on so the
2386
+ // cursor never claims a node twice.
2387
+ isAlreadyAdopted(tm, el) {
2388
+ if (el !== null && this.adopted.has(el)) {
2389
+ tm.state = "claimed";
2390
+ this.idx++;
2391
+ tm.comment.remove();
2392
+ return true;
2393
+ }
2394
+ return false;
2395
+ }
2396
+ recordAdopted(el) {
2397
+ this.adopted.add(el);
2398
+ }
2399
+ // Marker-only SSR can stack several `<!--vsk-->` marks before ONE element
2400
+ // (call-site marker + the callee's own root marker). The FIRST claim adopts
2401
+ // the element; the alias markers that immediately follow it are dead — they
2402
+ // point at an already-claimed node and must be physically retired so the
2403
+ // canary never counts them and a trailing alias never lingers in the DOM.
2404
+ retireAliases(el) {
2405
+ let i = this.idx;
2406
+ while (i < this.markers.length) {
2407
+ const tm = this.markers[i];
2408
+ if (tm.state === "claimed") {
2409
+ i++;
2410
+ continue;
2411
+ }
2412
+ if (tm.comment.nextElementSibling === el) {
2413
+ tm.state = "claimed";
2414
+ tm.comment.remove();
2415
+ i++;
2416
+ continue;
2417
+ }
2418
+ break;
2419
+ }
2420
+ }
2385
2421
  nextElement(tag) {
2386
2422
  while (this.idx < this.markers.length) {
2387
2423
  const tm = this.markers[this.idx];
@@ -2390,6 +2426,7 @@ var init_hydrate = __esm({
2390
2426
  continue;
2391
2427
  }
2392
2428
  const el = tm.comment.nextElementSibling;
2429
+ if (this.isAlreadyAdopted(tm, el)) continue;
2393
2430
  if (tag && el && el.tagName.toLowerCase() !== tag) {
2394
2431
  break;
2395
2432
  }
@@ -2399,6 +2436,8 @@ var init_hydrate = __esm({
2399
2436
  tm.state = "claimed";
2400
2437
  break;
2401
2438
  }
2439
+ this.recordAdopted(adopted);
2440
+ this.retireAliases(adopted);
2402
2441
  tm.state = "claimed";
2403
2442
  return adopted;
2404
2443
  }
@@ -2410,6 +2449,45 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2410
2449
  }
2411
2450
  return document.createElement(tag || "div");
2412
2451
  }
2452
+ claimOnly(tag) {
2453
+ while (this.idx < this.markers.length) {
2454
+ const tm = this.markers[this.idx];
2455
+ if (tm.state === "claimed") {
2456
+ this.idx++;
2457
+ continue;
2458
+ }
2459
+ const el = tm.comment.nextElementSibling;
2460
+ if (this.isAlreadyAdopted(tm, el)) continue;
2461
+ if (tag && el && el.tagName.toLowerCase() !== tag) break;
2462
+ this.idx++;
2463
+ const adopted = adoptElementRaw(tm.comment, tag);
2464
+ if (adopted === null) {
2465
+ tm.state = "claimed";
2466
+ break;
2467
+ }
2468
+ this.recordAdopted(adopted);
2469
+ this.retireAliases(adopted);
2470
+ tm.state = "claimed";
2471
+ return adopted;
2472
+ }
2473
+ return document.createElement(tag || "div");
2474
+ }
2475
+ retireDetached() {
2476
+ while (this.idx < this.markers.length) {
2477
+ const tm = this.markers[this.idx];
2478
+ if (tm.state === "claimed") {
2479
+ this.idx++;
2480
+ continue;
2481
+ }
2482
+ const c = tm.comment;
2483
+ if (!c || !c.parentNode) {
2484
+ tm.state = "claimed";
2485
+ this.idx++;
2486
+ continue;
2487
+ }
2488
+ break;
2489
+ }
2490
+ }
2413
2491
  subWalker(rootEl) {
2414
2492
  const subMarkers = this.markers.slice(this.idx).filter((m) => {
2415
2493
  if (m.state === "claimed") return false;
@@ -2420,9 +2498,6 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2420
2498
  });
2421
2499
  this.idx += subMarkers.length;
2422
2500
  for (const m of subMarkers) m.state = "claimed";
2423
- if (subMarkers.length === 0 && rootEl && rootEl.tagName === "SPAN" && rootEl.style && rootEl.style.display === "contents") {
2424
- return createHydrateChildWalker(rootEl);
2425
- }
2426
2501
  return new _WalkerEngine(rootEl, subMarkers.map((m) => m.comment));
2427
2502
  }
2428
2503
  claimByKey(key) {
@@ -2431,10 +2506,14 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2431
2506
  const tm = this.markers[i];
2432
2507
  if (tm.state === "claimed") continue;
2433
2508
  const el = tm.comment.nextElementSibling;
2509
+ if (this.isAlreadyAdopted(tm, el)) continue;
2434
2510
  if (el && el.getAttribute("data-vsk-key") === strKey) {
2435
2511
  tm.state = "claimed";
2512
+ this.recordAdopted(el);
2513
+ this.retireAliases(el);
2436
2514
  tm.comment.remove();
2437
2515
  stripDirectTextNodes(el);
2516
+ captureSsrElementChildren(el);
2438
2517
  stampClaimed(el);
2439
2518
  return { el };
2440
2519
  }
@@ -2448,6 +2527,7 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2448
2527
  const tm = this.markers[i];
2449
2528
  if (tm.state === "claimed") continue;
2450
2529
  const el = tm.comment.nextElementSibling;
2530
+ if (el && this.adopted.has(el)) continue;
2451
2531
  if (el && el.getAttribute("data-vsk-key") === strKey) return el;
2452
2532
  }
2453
2533
  return null;
@@ -2966,10 +3046,6 @@ function Link(props, registry, hydrate) {
2966
3046
  const href = props.href || "#";
2967
3047
  if (hydrate && hydrate.nextElement) {
2968
3048
  let a2 = hydrate.nextElement("a");
2969
- if (a2 && !a2.parentNode && hydrate.root) {
2970
- const existing = hydrate.root.querySelector("a");
2971
- if (existing) a2 = existing;
2972
- }
2973
3049
  const claimed = a2 ? a2.parentNode !== null : false;
2974
3050
  if (!a2) a2 = document.createElement("a");
2975
3051
  applyLinkDom(a2, props, href);
@@ -27563,15 +27639,21 @@ function emitStatic(ctx2, node, tracked2, effectsVar) {
27563
27639
  ctx2.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml4(attr.value))});`);
27564
27640
  }
27565
27641
  }
27642
+ let residueBefore = 0;
27566
27643
  for (const child of children) {
27567
27644
  const childVar = emitNode(ctx2, child, tracked2, effectsVar, el);
27568
27645
  if (childVar) {
27569
27646
  if (ctx2.hydrate) {
27570
- ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
27647
+ if (child instanceof TextNode || child instanceof DynamicBinding) {
27648
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { const __ssr = ${el}.__vsk_ssrEls || []; if (${residueBefore} < __ssr.length) ${el}.insertBefore(${childVar}, __ssr[${residueBefore}]); else ${el}.appendChild(${childVar}); }`);
27649
+ } else {
27650
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
27651
+ }
27571
27652
  } else {
27572
27653
  ctx2.push(`${el}.appendChild(${childVar});`);
27573
27654
  }
27574
27655
  }
27656
+ residueBefore += ssrResidueEstimate(child);
27575
27657
  }
27576
27658
  for (const attr of dynAttrs) {
27577
27659
  const target = attr.target || "";
@@ -27742,22 +27824,28 @@ function emitComponentCall(ctx2, node, tracked2, effectsVar, parentVar, compPref
27742
27824
  }
27743
27825
  const propsObj = `{ ${propsEntries.join(", ")} }`;
27744
27826
  const v = ctx2.n();
27745
- const awaitKw = ctx2.asyncComps.has(node.componentName) ? "await " : "";
27827
+ const awaitKw = ctx2.isAsyncScope ? "await " : "";
27746
27828
  const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
27747
27829
  if (ctx2.hydrate) {
27748
27830
  const access = calleeExpr ?? (ctx2.importedNames.has(node.componentName) ? node.componentName : `__components[${JSON.stringify(node.componentName)}]`);
27749
- const subScope = () => ctx2.inTryBody ? `${ctx2.walker}.subWalker(${parentVar})` : `${ctx2.walker}.subWalker(${ctx2.walker}.nextElement())`;
27831
+ const plainTarget = !!(calleeExpr || ctx2.importedNames.has(node.componentName)) && !ctx2.selfClaimNames.has(node.componentName);
27832
+ const walkerArg = ctx2.walker;
27833
+ const maybeReplace = (v2) => {
27834
+ if (!plainTarget) return;
27835
+ ctx2.push(`if (${v2} && ${v2}.parentNode == null && ${v2}.nodeType !== 11) { const __sr = ${walkerArg}.claimOnly(); if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v2}, __sr); }`);
27836
+ };
27837
+ const maybeRetire = () => {
27838
+ if (ctx2.selfClaimNames.has(node.componentName)) {
27839
+ ctx2.push(`${walkerArg}.retireDetached();`);
27840
+ }
27841
+ };
27750
27842
  if (node.children.length > 0) {
27751
- const walkerVar = ctx2.n();
27752
- ctx2.push(`const ${walkerVar} = ${subScope()};`);
27753
27843
  const frag = ctx2.n();
27754
27844
  ctx2.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
27755
- const savedWalker = ctx2.walker;
27756
27845
  const savedEffects = ctx2.effects;
27757
27846
  const wipeMount = ctx2.linkNames.has(node.componentName);
27758
27847
  const savedHydrate = ctx2.hydrate;
27759
27848
  if (wipeMount) ctx2.hydrate = false;
27760
- ctx2.walker = walkerVar;
27761
27849
  ctx2.effects = [];
27762
27850
  for (const child of node.children) {
27763
27851
  const childVar = emitNode(ctx2, child, tracked2, effectsVar, "$f");
@@ -27765,14 +27853,17 @@ function emitComponentCall(ctx2, node, tracked2, effectsVar, parentVar, compPref
27765
27853
  }
27766
27854
  for (const eff of ctx2.effects) ctx2.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
27767
27855
  ctx2.effects = savedEffects;
27768
- ctx2.walker = savedWalker;
27769
27856
  ctx2.hydrate = savedHydrate;
27770
27857
  ctx2.push(`return $f; })();`);
27771
27858
  propsEntries.push(`children: ${frag}`);
27772
- ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerVar});`);
27859
+ ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerArg});`);
27860
+ maybeReplace(v);
27861
+ maybeRetire();
27773
27862
  return v;
27774
27863
  }
27775
- ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${subScope()});`);
27864
+ ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${walkerArg});`);
27865
+ maybeReplace(v);
27866
+ maybeRetire();
27776
27867
  return v;
27777
27868
  } else {
27778
27869
  if (node.children.length > 0) {
@@ -28501,11 +28592,12 @@ ${renderAllItems(" ", null)}
28501
28592
  function computeAsyncComponents(comps) {
28502
28593
  return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
28503
28594
  }
28504
- function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set()) {
28595
+ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set(), selfClaimNames = /* @__PURE__ */ new Set()) {
28505
28596
  const tracked2 = collectTrackedNames(comp.body);
28506
28597
  const ctx2 = new Ctx();
28507
28598
  ctx2.importedNames = importedNames;
28508
28599
  ctx2.linkNames = linkNames;
28600
+ ctx2.selfClaimNames = selfClaimNames;
28509
28601
  ctx2.hydrate = hydrate;
28510
28602
  ctx2.asyncComps = asyncComps;
28511
28603
  ctx2.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
@@ -28529,10 +28621,16 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
28529
28621
  const paramInit = buildParamInit2(comp.paramNames);
28530
28622
  if (paramInit) ctx2.push(indent2(paramInit));
28531
28623
  for (const node of comp.body) {
28624
+ if (node instanceof TrackDecl) {
28625
+ emitNode(ctx2, node, tracked2, null);
28626
+ }
28627
+ }
28628
+ for (const node of comp.body) {
28629
+ if (node instanceof TrackDecl) continue;
28532
28630
  const v = emitNode(ctx2, node, tracked2, null);
28533
28631
  if (v) {
28534
28632
  if (ctx2.hydrate) {
28535
- ctx2.push(indent2(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
28633
+ ctx2.push(indent2(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !${v}.parentNode.contains(${v})) $root.appendChild(${v}); }`));
28536
28634
  } else {
28537
28635
  ctx2.push(indent2(`$root.appendChild(${v});`));
28538
28636
  }
@@ -28559,7 +28657,13 @@ function buildComponentMap(irRoot, hydrate = false) {
28559
28657
  mapLines.push(`const __components = {};`);
28560
28658
  const asyncComps = computeAsyncComponents(irRoot.components);
28561
28659
  const linkNames = /* @__PURE__ */ new Set();
28660
+ const selfClaimNames = /* @__PURE__ */ new Set();
28661
+ const SELF_CLAIM_RUNTIME = /* @__PURE__ */ new Set(["Link", "NavLink", "Md", "Form", "Field", "LoadingIndicator"]);
28562
28662
  for (const imp of irRoot.imports) {
28663
+ if (importModuleTarget(imp) !== "@vesk/runtime") continue;
28664
+ for (const pair of importBindingPairs(imp)) {
28665
+ if (SELF_CLAIM_RUNTIME.has(pair.imported)) selfClaimNames.add(pair.local);
28666
+ }
28563
28667
  for (const pair of importBindingPairs(imp)) {
28564
28668
  if (pair.imported === "Link" || pair.imported === "NavLink") linkNames.add(pair.local);
28565
28669
  }
@@ -28569,11 +28673,11 @@ function buildComponentMap(irRoot, hydrate = false) {
28569
28673
  const directNames = /* @__PURE__ */ new Set([...extractRuntimeNames(irRoot.imports), ...localValueImportNames(irRoot.imports), ...topValueNames]);
28570
28674
  for (const comp of irRoot.components) {
28571
28675
  if (hydrate && isStaticComponent(comp)) {
28572
- const stub = `(props, __registry, __hydrate) => { return __hydrate.root; }`;
28676
+ const stub = `(props, __registry, __hydrate) => { return __hydrate.claimOnly(); }`;
28573
28677
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
28574
28678
  continue;
28575
28679
  }
28576
- const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames);
28680
+ const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
28577
28681
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
28578
28682
  }
28579
28683
  mapLines.push(`function __cleanup(start, end) {`);
@@ -28602,6 +28706,32 @@ function buildComponentMap(irRoot, hydrate = false) {
28602
28706
  mapLines.push(`}`);
28603
28707
  return mapLines.join("\n\n");
28604
28708
  }
28709
+ function ssrResidueEstimate(node) {
28710
+ if (Array.isArray(node)) {
28711
+ let total = 0;
28712
+ for (const n of node) total += ssrResidueEstimate(n);
28713
+ return total;
28714
+ }
28715
+ if (node instanceof StaticNode) return 1;
28716
+ if (node instanceof ComponentCall) return 1;
28717
+ if (node instanceof OpaqueDynamicRegion) {
28718
+ return Math.max(ssrResidueEstimate(node.consequentNodes), ssrResidueEstimate(node.alternateNodes));
28719
+ }
28720
+ if (node instanceof MapRegion) {
28721
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.alternateNodes));
28722
+ }
28723
+ if (node instanceof ForLoop) return ssrResidueEstimate(node.bodyTemplate);
28724
+ if (node instanceof WhileLoop) return ssrResidueEstimate(node.bodyTemplate);
28725
+ if (node instanceof SwitchBlock) {
28726
+ let max = 0;
28727
+ for (const c of node.cases) max = Math.max(max, ssrResidueEstimate(c.body));
28728
+ return max;
28729
+ }
28730
+ if (node instanceof TryCatch) {
28731
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.catchBody));
28732
+ }
28733
+ return 0;
28734
+ }
28605
28735
  function isStaticIR(body) {
28606
28736
  for (const node of body) {
28607
28737
  if (node instanceof StaticNode) {
@@ -28831,6 +28961,7 @@ var init_client_codegen = __esm({
28831
28961
  init_server_utils();
28832
28962
  init_module_imports();
28833
28963
  init_scan();
28964
+ init_tokens();
28834
28965
  init_md_inline();
28835
28966
  init_strip_ts();
28836
28967
  NON_BUBBLING_EVENTS = /* @__PURE__ */ new Set([
@@ -28872,6 +29003,11 @@ var init_client_codegen = __esm({
28872
29003
  c = 0;
28873
29004
  importedNames = /* @__PURE__ */ new Set();
28874
29005
  linkNames = /* @__PURE__ */ new Set();
29006
+ // Runtime component imports that claim their own SSR root via `nextElement`
29007
+ // (Link/NavLink/Md/Form/Field/LoadingIndicator) and return a DocumentFragment
29008
+ // when claimed. They need no call-site adoption guard — the guard targets
29009
+ // genuinely-plain components that never touch the walker.
29010
+ selfClaimNames = /* @__PURE__ */ new Set();
28875
29011
  delegatedEvents = /* @__PURE__ */ new Set();
28876
29012
  directEvents = /* @__PURE__ */ new Set();
28877
29013
  hydrate = false;
@@ -29260,7 +29396,7 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked2) {
29260
29396
  lines.push(`const ${calleeVar} = ${callee};`);
29261
29397
  const callExpr3 = `${awaitKw}${calleeVar}(${propsObj}, __registry, (${calleeVar}.__veskScope || __vesk))`;
29262
29398
  if (__vskHydrate) {
29263
- lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr3} || '') + '</span>');`);
29399
+ lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr3} || ''));`);
29264
29400
  } else {
29265
29401
  lines.push(`__out.push(${callExpr3} || '');`);
29266
29402
  }
@@ -29364,7 +29500,7 @@ var init_server_jsgen = __esm({
29364
29500
  init_scan();
29365
29501
  init_server_utils();
29366
29502
  init_module_imports();
29367
- HYDRATE_COMPONENT_WRAPPER = '<!--vsk--><span style="display:contents">';
29503
+ HYDRATE_COMPONENT_WRAPPER = "<!--vsk-->";
29368
29504
  __currentCompName = "";
29369
29505
  }
29370
29506
  });
@@ -40811,6 +40947,7 @@ init_actions();
40811
40947
  init_server_utils();
40812
40948
  init_module_imports();
40813
40949
  init_scan();
40950
+ init_tokens();
40814
40951
  init_md_inline();
40815
40952
  init_strip_ts();
40816
40953
  import { walk as walk6 } from "zimmerframe";
@@ -41032,6 +41169,11 @@ var Ctx2 = class {
41032
41169
  c = 0;
41033
41170
  importedNames = /* @__PURE__ */ new Set();
41034
41171
  linkNames = /* @__PURE__ */ new Set();
41172
+ // Runtime component imports that claim their own SSR root via `nextElement`
41173
+ // (Link/NavLink/Md/Form/Field/LoadingIndicator) and return a DocumentFragment
41174
+ // when claimed. They need no call-site adoption guard — the guard targets
41175
+ // genuinely-plain components that never touch the walker.
41176
+ selfClaimNames = /* @__PURE__ */ new Set();
41035
41177
  delegatedEvents = /* @__PURE__ */ new Set();
41036
41178
  directEvents = /* @__PURE__ */ new Set();
41037
41179
  hydrate = false;
@@ -41189,15 +41331,21 @@ function emitStatic2(ctx2, node, tracked2, effectsVar) {
41189
41331
  ctx2.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml5(attr.value))});`);
41190
41332
  }
41191
41333
  }
41334
+ let residueBefore = 0;
41192
41335
  for (const child of children) {
41193
41336
  const childVar = emitNode2(ctx2, child, tracked2, effectsVar, el);
41194
41337
  if (childVar) {
41195
41338
  if (ctx2.hydrate) {
41196
- ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
41339
+ if (child instanceof TextNode || child instanceof DynamicBinding) {
41340
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { const __ssr = ${el}.__vsk_ssrEls || []; if (${residueBefore} < __ssr.length) ${el}.insertBefore(${childVar}, __ssr[${residueBefore}]); else ${el}.appendChild(${childVar}); }`);
41341
+ } else {
41342
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
41343
+ }
41197
41344
  } else {
41198
41345
  ctx2.push(`${el}.appendChild(${childVar});`);
41199
41346
  }
41200
41347
  }
41348
+ residueBefore += ssrResidueEstimate2(child);
41201
41349
  }
41202
41350
  for (const attr of dynAttrs) {
41203
41351
  const target = attr.target || "";
@@ -41388,23 +41536,30 @@ function emitComponentCall2(ctx2, node, tracked2, effectsVar, parentVar, compPre
41388
41536
  }
41389
41537
  const propsObj = `{ ${propsEntries.join(", ")} }`;
41390
41538
  const v = ctx2.n();
41391
- const awaitKw = ctx2.asyncComps.has(node.componentName) ? "await " : "";
41539
+ const awaitKw = ctx2.isAsyncScope ? "await " : "";
41392
41540
  const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
41393
41541
  if (ctx2.hydrate) {
41394
41542
  const access = calleeExpr ?? (ctx2.importedNames.has(node.componentName) ? node.componentName : `__components[${JSON.stringify(node.componentName)}]`);
41395
- const subScope = () => ctx2.inTryBody ? `${ctx2.walker}.subWalker(${parentVar})` : `${ctx2.walker}.subWalker(${ctx2.walker}.nextElement())`;
41543
+ const plainTarget = !!(calleeExpr || ctx2.importedNames.has(node.componentName)) && !ctx2.selfClaimNames.has(node.componentName);
41544
+ const walkerArg = ctx2.walker;
41545
+ const maybeReplace = (v2) => {
41546
+ if (!plainTarget)
41547
+ return;
41548
+ ctx2.push(`if (${v2} && ${v2}.parentNode == null && ${v2}.nodeType !== 11) { const __sr = ${walkerArg}.claimOnly(); if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v2}, __sr); }`);
41549
+ };
41550
+ const maybeRetire = () => {
41551
+ if (ctx2.selfClaimNames.has(node.componentName)) {
41552
+ ctx2.push(`${walkerArg}.retireDetached();`);
41553
+ }
41554
+ };
41396
41555
  if (node.children.length > 0) {
41397
- const walkerVar = ctx2.n();
41398
- ctx2.push(`const ${walkerVar} = ${subScope()};`);
41399
41556
  const frag = ctx2.n();
41400
41557
  ctx2.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
41401
- const savedWalker = ctx2.walker;
41402
41558
  const savedEffects = ctx2.effects;
41403
41559
  const wipeMount = ctx2.linkNames.has(node.componentName);
41404
41560
  const savedHydrate = ctx2.hydrate;
41405
41561
  if (wipeMount)
41406
41562
  ctx2.hydrate = false;
41407
- ctx2.walker = walkerVar;
41408
41563
  ctx2.effects = [];
41409
41564
  for (const child of node.children) {
41410
41565
  const childVar = emitNode2(ctx2, child, tracked2, effectsVar, "$f");
@@ -41414,14 +41569,17 @@ function emitComponentCall2(ctx2, node, tracked2, effectsVar, parentVar, compPre
41414
41569
  for (const eff of ctx2.effects)
41415
41570
  ctx2.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr2(eff)});` : eff);
41416
41571
  ctx2.effects = savedEffects;
41417
- ctx2.walker = savedWalker;
41418
41572
  ctx2.hydrate = savedHydrate;
41419
41573
  ctx2.push(`return $f; })();`);
41420
41574
  propsEntries.push(`children: ${frag}`);
41421
- ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerVar});`);
41575
+ ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerArg});`);
41576
+ maybeReplace(v);
41577
+ maybeRetire();
41422
41578
  return v;
41423
41579
  }
41424
- ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${subScope()});`);
41580
+ ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${walkerArg});`);
41581
+ maybeReplace(v);
41582
+ maybeRetire();
41425
41583
  return v;
41426
41584
  } else {
41427
41585
  if (node.children.length > 0) {
@@ -42235,11 +42393,12 @@ ${renderAllItems(" ", null)}
42235
42393
  function computeAsyncComponents2(comps) {
42236
42394
  return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
42237
42395
  }
42238
- function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set()) {
42396
+ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set(), selfClaimNames = /* @__PURE__ */ new Set()) {
42239
42397
  const tracked2 = collectTrackedNames2(comp.body);
42240
42398
  const ctx2 = new Ctx2();
42241
42399
  ctx2.importedNames = importedNames;
42242
42400
  ctx2.linkNames = linkNames;
42401
+ ctx2.selfClaimNames = selfClaimNames;
42243
42402
  ctx2.hydrate = hydrate;
42244
42403
  ctx2.asyncComps = asyncComps;
42245
42404
  ctx2.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
@@ -42264,10 +42423,17 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
42264
42423
  if (paramInit)
42265
42424
  ctx2.push(indent3(paramInit));
42266
42425
  for (const node of comp.body) {
42426
+ if (node instanceof TrackDecl) {
42427
+ emitNode2(ctx2, node, tracked2, null);
42428
+ }
42429
+ }
42430
+ for (const node of comp.body) {
42431
+ if (node instanceof TrackDecl)
42432
+ continue;
42267
42433
  const v = emitNode2(ctx2, node, tracked2, null);
42268
42434
  if (v) {
42269
42435
  if (ctx2.hydrate) {
42270
- ctx2.push(indent3(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
42436
+ ctx2.push(indent3(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !${v}.parentNode.contains(${v})) $root.appendChild(${v}); }`));
42271
42437
  } else {
42272
42438
  ctx2.push(indent3(`$root.appendChild(${v});`));
42273
42439
  }
@@ -42298,7 +42464,15 @@ function buildComponentMap3(irRoot, hydrate = false) {
42298
42464
  mapLines.push(`const __components = {};`);
42299
42465
  const asyncComps = computeAsyncComponents2(irRoot.components);
42300
42466
  const linkNames = /* @__PURE__ */ new Set();
42467
+ const selfClaimNames = /* @__PURE__ */ new Set();
42468
+ const SELF_CLAIM_RUNTIME = /* @__PURE__ */ new Set(["Link", "NavLink", "Md", "Form", "Field", "LoadingIndicator"]);
42301
42469
  for (const imp of irRoot.imports) {
42470
+ if (importModuleTarget(imp) !== "@vesk/runtime")
42471
+ continue;
42472
+ for (const pair of importBindingPairs(imp)) {
42473
+ if (SELF_CLAIM_RUNTIME.has(pair.imported))
42474
+ selfClaimNames.add(pair.local);
42475
+ }
42302
42476
  for (const pair of importBindingPairs(imp)) {
42303
42477
  if (pair.imported === "Link" || pair.imported === "NavLink")
42304
42478
  linkNames.add(pair.local);
@@ -42309,11 +42483,11 @@ function buildComponentMap3(irRoot, hydrate = false) {
42309
42483
  const directNames = /* @__PURE__ */ new Set([...extractRuntimeNames(irRoot.imports), ...localValueImportNames(irRoot.imports), ...topValueNames]);
42310
42484
  for (const comp of irRoot.components) {
42311
42485
  if (hydrate && isStaticComponent2(comp)) {
42312
- const stub = `(props, __registry, __hydrate) => { return __hydrate.root; }`;
42486
+ const stub = `(props, __registry, __hydrate) => { return __hydrate.claimOnly(); }`;
42313
42487
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
42314
42488
  continue;
42315
42489
  }
42316
- const code = generateComponent2(comp, directNames, hydrate, asyncComps, linkNames);
42490
+ const code = generateComponent2(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
42317
42491
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
42318
42492
  }
42319
42493
  mapLines.push(`function __cleanup(start, end) {`);
@@ -42342,6 +42516,38 @@ function buildComponentMap3(irRoot, hydrate = false) {
42342
42516
  mapLines.push(`}`);
42343
42517
  return mapLines.join("\n\n");
42344
42518
  }
42519
+ function ssrResidueEstimate2(node) {
42520
+ if (Array.isArray(node)) {
42521
+ let total = 0;
42522
+ for (const n of node)
42523
+ total += ssrResidueEstimate2(n);
42524
+ return total;
42525
+ }
42526
+ if (node instanceof StaticNode)
42527
+ return 1;
42528
+ if (node instanceof ComponentCall)
42529
+ return 1;
42530
+ if (node instanceof OpaqueDynamicRegion) {
42531
+ return Math.max(ssrResidueEstimate2(node.consequentNodes), ssrResidueEstimate2(node.alternateNodes));
42532
+ }
42533
+ if (node instanceof MapRegion) {
42534
+ return Math.max(ssrResidueEstimate2(node.bodyTemplate), ssrResidueEstimate2(node.alternateNodes));
42535
+ }
42536
+ if (node instanceof ForLoop)
42537
+ return ssrResidueEstimate2(node.bodyTemplate);
42538
+ if (node instanceof WhileLoop)
42539
+ return ssrResidueEstimate2(node.bodyTemplate);
42540
+ if (node instanceof SwitchBlock) {
42541
+ let max = 0;
42542
+ for (const c of node.cases)
42543
+ max = Math.max(max, ssrResidueEstimate2(c.body));
42544
+ return max;
42545
+ }
42546
+ if (node instanceof TryCatch) {
42547
+ return Math.max(ssrResidueEstimate2(node.bodyTemplate), ssrResidueEstimate2(node.catchBody));
42548
+ }
42549
+ return 0;
42550
+ }
42345
42551
  function isStaticIR2(body) {
42346
42552
  for (const node of body) {
42347
42553
  if (node instanceof StaticNode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.25",
12
- "@vesk/compiler": "^0.2.25",
13
- "@vesk/plugin-tailwind": "^0.2.25",
14
- "@vesk/runtime": "^0.2.25",
11
+ "@vesk/adapter": "^0.2.26",
12
+ "@vesk/compiler": "^0.2.26",
13
+ "@vesk/plugin-tailwind": "^0.2.26",
14
+ "@vesk/runtime": "^0.2.26",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",