@vesk/compiler 0.2.92 → 0.2.94

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.
@@ -774,6 +774,7 @@ function generateJsxParser(acorn, acornTypeScript, Parser, jsxOptions) {
774
774
  return node;
775
775
  case tok.jsxTagStart:
776
776
  case tt.string:
777
+ case tt.backQuote:
777
778
  return this.parseExprAtom();
778
779
  default:
779
780
  this.raise(this.start, "JSX value should be either an expression or a quoted JSX text");
@@ -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,EAUjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,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,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAI5E;AAyCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoFD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AAupED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAuKD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAuED,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,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;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,EAUjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,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,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAI5E;AAyCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoFD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AA0uED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAuKD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAwED,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,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -539,6 +539,15 @@ class Ctx {
539
539
  // item element already claimed by key) instead of walking for a new element.
540
540
  rootClaim = false;
541
541
  walker = '__hydrate';
542
+ // Registry a hydrate-mode component call must resolve through. The hydrate
543
+ // bundle registers its functions under `__hydrators`, while non-hydrate
544
+ // nested renders (children of self-claiming components, client-only blocks,
545
+ // fresh map items) must keep calling the plain `__components` map. Emitting
546
+ // the hydrate registry name directly avoids the old global
547
+ // `__components` → `__hydrators` text rewrite, which also rewrote those
548
+ // nested fresh-render calls and handed them the 3-argument hydrator with no
549
+ // walker (`Cannot read properties of undefined (reading 'root')`).
550
+ hydrateRegistry = '__hydrators';
542
551
  asyncComps = new Set();
543
552
  isAsyncScope = false;
544
553
  // While emitting a loop body, names the boolean flag a `break` sets (see
@@ -676,6 +685,7 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
676
685
  ctx.push(` const __slot = createLayoutSlot(${ctx.walker}, ${parentVar});`);
677
686
  ctx.push(` const __child = props.children(__slot.walker);`);
678
687
  ctx.push(` if (__child && typeof __child.then === 'function') __pendingChild = __slot.track(__child);`);
688
+ ctx.push(` else if (__child && __child.nodeType === 11) ${parentVar}.appendChild(__child);`);
679
689
  ctx.push(` } else {`);
680
690
  ctx.push(` ${parentVar}.appendChild(props.children);`);
681
691
  ctx.push(` }`);
@@ -1260,6 +1270,65 @@ function emitFragment(ctx, nodes, tracked, effectsVar) {
1260
1270
  ctx.push(`return $f; })();`);
1261
1271
  return frag;
1262
1272
  }
1273
+ /**
1274
+ * True when any node in the subtree is a layout `{props.children}` slot.
1275
+ *
1276
+ * A slot must be hydrated on the walker scoped to the element that will
1277
+ * actually contain it. Building the children fragment eagerly on the caller's
1278
+ * walker is only correct when the callee appends that fragment to its own
1279
+ * mount point. A component that forwards children through a scoped container
1280
+ * (e.g. a shell that renders `{props.children}` inside `<main>`) invokes the
1281
+ * child hydrator with a sub-walker; passing an eagerly-built fragment skips
1282
+ * that scope and lets the page claim the shell's own SSR root.
1283
+ */
1284
+ function containsSlot(nodes) {
1285
+ for (const node of nodes) {
1286
+ if (node instanceof SlotNode)
1287
+ return true;
1288
+ if (node instanceof StaticNode) {
1289
+ if (containsSlot(node.children))
1290
+ return true;
1291
+ }
1292
+ else if (node instanceof ComponentCall) {
1293
+ if (containsSlot(node.children))
1294
+ return true;
1295
+ }
1296
+ else if (node instanceof OpaqueDynamicRegion) {
1297
+ if (containsSlot(node.consequentNodes) || containsSlot(node.alternateNodes))
1298
+ return true;
1299
+ }
1300
+ else if (node instanceof MapRegion) {
1301
+ if (containsSlot(node.bodyTemplate) || containsSlot(node.alternateNodes))
1302
+ return true;
1303
+ }
1304
+ else if (node instanceof WhileLoop) {
1305
+ if (containsSlot(node.bodyTemplate))
1306
+ return true;
1307
+ }
1308
+ else if (node instanceof ForLoop) {
1309
+ if (containsSlot(node.bodyTemplate))
1310
+ return true;
1311
+ }
1312
+ else if (node instanceof SwitchBlock) {
1313
+ for (const c of node.cases)
1314
+ if (containsSlot(c.body))
1315
+ return true;
1316
+ }
1317
+ else if (node instanceof TryCatch) {
1318
+ if (containsSlot(node.bodyTemplate) || containsSlot(node.catchBody))
1319
+ return true;
1320
+ }
1321
+ else if (node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
1322
+ if (containsSlot(node.children))
1323
+ return true;
1324
+ }
1325
+ else if (node instanceof PropSlot) {
1326
+ if (containsSlot(node.body))
1327
+ return true;
1328
+ }
1329
+ }
1330
+ return false;
1331
+ }
1263
1332
  function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__components', skipK = 0, regionBudget) {
1264
1333
  const propsEntries = node.props.map((p) => {
1265
1334
  if (typeof p.value === 'string')
@@ -1322,7 +1391,7 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1322
1391
  const access = calleeExpr
1323
1392
  ?? (ctx.importedNames.has(node.componentName)
1324
1393
  ? node.componentName
1325
- : `__components[${JSON.stringify(node.componentName)}]`);
1394
+ : `${ctx.hydrateRegistry}[${JSON.stringify(node.componentName)}]`);
1326
1395
  // Registry lookups can resolve to `undefined` when a JSX tag names a
1327
1396
  // component that was never declared, imported, or registered (e.g.
1328
1397
  // `<Slot/>` in a framework where `{props.children}` is the channel).
@@ -1397,20 +1466,56 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1397
1466
  // fresh and let the runtime component adopt the existing root.
1398
1467
  if (wipeMount || ctx.selfClaimNames.has(node.componentName))
1399
1468
  ctx.hydrate = false;
1400
- ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
1401
- const savedEffects = ctx.effects;
1402
- ctx.effects = [];
1403
- for (const child of regChildren) {
1404
- const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
1405
- if (childVar)
1406
- ctx.push(`$f.appendChild(${childVar});`);
1469
+ // A slot nested under a compiled component must be built by that
1470
+ // component's scoped walker, not the caller's. Pass children as a lazy
1471
+ // hydrator so the callee can invoke it with the walker for the element
1472
+ // that actually contains the slot (e.g. `<main>` in a shell). Eagerly
1473
+ // building the fragment here hydrates the page against the shell's own
1474
+ // SSR root, which detaches the shell on hydration.
1475
+ const lazySlotChildren = ctx.hydrate && !plainTarget && !ctx.selfClaimNames.has(node.componentName) && containsSlot(regChildren);
1476
+ if (lazySlotChildren) {
1477
+ const fnVar = ctx.n();
1478
+ const savedLines = ctx.lines;
1479
+ const savedEffects = ctx.effects;
1480
+ const savedWalker = ctx.walker;
1481
+ ctx.lines = [];
1482
+ ctx.effects = [];
1483
+ ctx.walker = '__childWalker';
1484
+ ctx.push(`const $f = document.createDocumentFragment();`);
1485
+ for (const child of regChildren) {
1486
+ const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
1487
+ if (childVar)
1488
+ ctx.push(`$f.appendChild(${childVar});`);
1489
+ }
1490
+ for (const eff of ctx.effects)
1491
+ ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
1492
+ ctx.push(`return $f;`);
1493
+ const body = ctx.lines.join('\n');
1494
+ ctx.lines = savedLines;
1495
+ ctx.effects = savedEffects;
1496
+ ctx.walker = savedWalker;
1497
+ const asyncKw = ctx.isAsyncScope ? 'async ' : '';
1498
+ ctx.push(`const ${fnVar} = ${asyncKw}(__childWalker) => {`);
1499
+ ctx.push(indent(body));
1500
+ ctx.push(`};`);
1501
+ propsEntries.push(`children: ${fnVar}`);
1502
+ }
1503
+ else {
1504
+ ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
1505
+ const savedEffects = ctx.effects;
1506
+ ctx.effects = [];
1507
+ for (const child of regChildren) {
1508
+ const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
1509
+ if (childVar)
1510
+ ctx.push(`$f.appendChild(${childVar});`);
1511
+ }
1512
+ for (const eff of ctx.effects)
1513
+ ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
1514
+ ctx.effects = savedEffects;
1515
+ ctx.push(`return $f; })();`);
1516
+ propsEntries.push(`children: ${frag}`);
1407
1517
  }
1408
- for (const eff of ctx.effects)
1409
- ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
1410
- ctx.effects = savedEffects;
1411
1518
  ctx.hydrate = savedHydrate;
1412
- ctx.push(`return $f; })();`);
1413
- propsEntries.push(`children: ${frag}`);
1414
1519
  }
1415
1520
  // Deposit the residue for the child's first top-level claim (it runs on
1416
1521
  // this same walker with its own zero `topPendingSkip`). A region budget is
@@ -1658,7 +1763,7 @@ function emitTryCatch(ctx, node, tracked, effectsVar, parentVar, skipK = 0, regi
1658
1763
  ${effArr}.length = 0;
1659
1764
  __cleanup(${anchor}, ${endAnchor});
1660
1765
  try { ${asyncKw}${tryRenderComp}(); } catch(${catchParam}) { ${hasCatch ? `${asyncKw}${catchRenderComp}(${catchParam});` : 'throw ${catchParam};'} }
1661
- });
1766
+ }, true);
1662
1767
  }`);
1663
1768
  return null;
1664
1769
  }
@@ -1757,7 +1862,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
1757
1862
  ${effInner}
1758
1863
  __iv = __new;
1759
1864
  }
1760
- });`;
1865
+ }, true);`;
1761
1866
  if (effTarget) {
1762
1867
  ctx.push(`${effTarget}.push((() => {
1763
1868
  let __iv = ${branchInit};
@@ -1858,7 +1963,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
1858
1963
  ${renderBranch}
1859
1964
  __iv = __nv;
1860
1965
  }
1861
- });`;
1966
+ }, true);`;
1862
1967
  // A static condition renders once; an effect referencing it would only ever
1863
1968
  // reference statement/loop-local bindings that are out of scope at the flush
1864
1969
  // site (e.g. `const pages = …; if (pages.length > 0)` inside a for-of body).
@@ -1964,7 +2069,7 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1964
2069
  __busy = false;
1965
2070
  __iv = (${condExpr});
1966
2071
  }
1967
- });
2072
+ }, true);
1968
2073
  }`);
1969
2074
  return null;
1970
2075
  }
@@ -2078,7 +2183,7 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
2078
2183
  __busy = false;
2079
2184
  __iv = (${srcExpr});
2080
2185
  }
2081
- });
2186
+ }, true);
2082
2187
  }`);
2083
2188
  }
2084
2189
  else {
@@ -2147,7 +2252,7 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
2147
2252
  __cleanup(${anchor}, ${endAnchor});
2148
2253
  ${ctx.isAsyncScope ? 'await ' : ''}${renderLoop}();
2149
2254
  __busy = false;
2150
- });
2255
+ }, true);
2151
2256
  }`);
2152
2257
  }
2153
2258
  }
@@ -2224,7 +2329,7 @@ function emitSwitchBlock(ctx, node, tracked, parentVar, skipK = 0, regionBudget)
2224
2329
  __busy = false;
2225
2330
  __iv = (${discExpr});
2226
2331
  }
2227
- });
2332
+ }, true);
2228
2333
  }`);
2229
2334
  return null;
2230
2335
  }
@@ -2447,7 +2552,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2447
2552
  ${effectsVar}.length = 0;
2448
2553
  __cleanup(${anchor}, ${endAnchor});
2449
2554
  if (__new) { ${initList}(); } else { ${aw}${emptyRenderName}(); }
2450
- })`);
2555
+ }, true)`);
2451
2556
  }
2452
2557
  else {
2453
2558
  ctx.push(`${initList}();`);
@@ -2457,7 +2562,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2457
2562
  const __nv = ${arrExpr};
2458
2563
  if (__first) { __first = false; __nv; ${breakReset}${reconciler}(${arrExpr}); return; }
2459
2564
  ${breakVar ? `if (${breakVar}) ${breakVar} = false;` : ''}${reconciler}(${arrExpr});
2460
- })`);
2565
+ }, true)`);
2461
2566
  }
2462
2567
  }
2463
2568
  else {
@@ -2515,7 +2620,7 @@ ${renderAllItems(' ', null)}
2515
2620
  if (__new) {
2516
2621
  ${renderAllItems(' ', null)}
2517
2622
  } else { ${aw}${emptyRenderName}(${collectVar ? collectVar : ''}); }
2518
- })`);
2623
+ }, true)`);
2519
2624
  }
2520
2625
  else {
2521
2626
  const collectVar = hyd ? ctx.n() : null;
@@ -2533,7 +2638,7 @@ ${renderAllItems(' ', null)}
2533
2638
  ${effectsVar}.length = 0;
2534
2639
  __cleanup(${anchor}, ${endAnchor});
2535
2640
  ${renderAllItems(' ', null)}
2536
- })`);
2641
+ }, true)`);
2537
2642
  }
2538
2643
  }
2539
2644
  return null;
@@ -2746,7 +2851,8 @@ function buildParamInit(paramNames, propsAlias = null) {
2746
2851
  }
2747
2852
  function buildComponentMap(irRoot, hydrate = false, alloc, markerless = true) {
2748
2853
  const mapLines = [];
2749
- mapLines.push(`const __components = {};`);
2854
+ const registry = hydrate ? '__hydrators' : '__components';
2855
+ mapLines.push(`const ${registry} = {};`);
2750
2856
  const asyncComps = computeAsyncComponents(irRoot.components);
2751
2857
  // Wipe-style router components (Link/NavLink) replace their SSR children on
2752
2858
  // hydration, so their children fragments must be rebuilt in full. Track the
@@ -2788,11 +2894,11 @@ function buildComponentMap(irRoot, hydrate = false, alloc, markerless = true) {
2788
2894
  ? `__hydrate.claimOnly(undefined, (typeof __hydrate.takeSkipK === 'function' ? __hydrate.takeSkipK() : 0))`
2789
2895
  : `__hydrate.claimOnly()`;
2790
2896
  const stub = `(props, __registry, __hydrate) => { return ${claimExpr}; }`;
2791
- mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
2897
+ mapLines.push(`${registry}[${JSON.stringify(comp.name)}] = ${stub};`);
2792
2898
  continue;
2793
2899
  }
2794
2900
  const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames, alloc, markerless);
2795
- mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
2901
+ mapLines.push(`${registry}[${JSON.stringify(comp.name)}] = ${code};`);
2796
2902
  }
2797
2903
  mapLines.push(`function __cleanup(start, end) {`);
2798
2904
  mapLines.push(`\tlet n = start.nextSibling;`);
@@ -3117,13 +3223,14 @@ function emitClientFromIR(ir, options) {
3117
3223
  const importLines = ir.imports.length > 0 ? ir.imports.join('\n') + '\n' : '';
3118
3224
  const topCode = (options.includeTopLevel === false ? [] : transformTopLevelForActions(ir.topLevelCode, 'client')).join('\n') + '\n';
3119
3225
  const exportLines = [];
3226
+ const registry = options.hydrate ? '__hydrators' : '__components';
3120
3227
  for (const comp of ir.components) {
3121
3228
  if (comp.exported) {
3122
3229
  if (comp.defaultExport) {
3123
- exportLines.push(`export default __components[${JSON.stringify(comp.name)}];`);
3230
+ exportLines.push(`export default ${registry}[${JSON.stringify(comp.name)}];`);
3124
3231
  }
3125
3232
  else {
3126
- exportLines.push(`export const ${comp.name} = __components[${JSON.stringify(comp.name)}];`);
3233
+ exportLines.push(`export const ${comp.name} = ${registry}[${JSON.stringify(comp.name)}];`);
3127
3234
  }
3128
3235
  }
3129
3236
  }
package/dist/errors.d.ts CHANGED
@@ -104,6 +104,34 @@ export declare class VeskError extends Error {
104
104
  static attrJsxElement(context?: VeskErrorOptions & {
105
105
  attr?: string;
106
106
  }): VeskError;
107
+ /**
108
+ * A `{ ... }` block statement wrapping regular JS in statement mode.
109
+ * Vesk statement mode doesn't need braces around statements —
110
+ * `if (cond) { <jsx/> }` is written without the outer `{ }`.
111
+ */
112
+ static blockStatementInStatementMode(context?: VeskErrorOptions): VeskError;
113
+ /**
114
+ * An opening JSX tag was never closed, e.g. `<div><span>hello</div>`.
115
+ * Acorn reports this as "Expected corresponding JSX closing tag for <span>"
116
+ * or, when wrapped in a component body, as a generic "Unexpected token".
117
+ */
118
+ static jsxUnclosedTag(tagName: string, context?: VeskErrorOptions & {
119
+ closingTag?: string;
120
+ suggestedFix?: string;
121
+ }): VeskError;
122
+ /**
123
+ * A JSX closing tag doesn't match the most recent opening tag,
124
+ * e.g. `<div></span>`.
125
+ */
126
+ static jsxMismatchedTag(openingTag: string, closingTag: string, context?: VeskErrorOptions): VeskError;
127
+ /**
128
+ * JSX content was never terminated before end of file, e.g. `<div>` with no `>`.
129
+ */
130
+ static jsxUnterminated(context?: VeskErrorOptions): VeskError;
131
+ /**
132
+ * An empty JSX expression container `{}`, which is not allowed.
133
+ */
134
+ static jsxEmptyExpression(context?: VeskErrorOptions): VeskError;
107
135
  toString(): string;
108
136
  toJSON(): Record<string, unknown>;
109
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js CHANGED
@@ -389,38 +389,146 @@ export class VeskError extends Error {
389
389
  tip: 'JSX elements are content. They render inside an element or travel as a component prop — never as an attribute value on an HTML tag.',
390
390
  });
391
391
  }
392
+ /**
393
+ * A `{ ... }` block statement wrapping regular JS in statement mode.
394
+ * Vesk statement mode doesn't need braces around statements —
395
+ * `if (cond) { <jsx/> }` is written without the outer `{ }`.
396
+ */
397
+ static blockStatementInStatementMode(context = {}) {
398
+ return new VeskError('Unnecessary `{ ... }` block around statements in statement mode. Vesk statement mode does not use braces around `if`/`for`/`while`/etc. — write the statement directly.', {
399
+ ...context,
400
+ code: context.code || 'V0424',
401
+ suggestions: [
402
+ 'Remove the outer `{ }` and write the statement directly:',
403
+ ' `{ if (cond) { <div/> } }` → `if (cond) { <div/> }`',
404
+ 'Braces are only needed around JSX expression containers `{ expr }` inside JSX, not around statements.',
405
+ ],
406
+ nextSteps: [
407
+ 'Delete the opening `{` and closing `}` that wrap the statement.',
408
+ 'If you meant to group multiple statements, they can appear directly one after another in statement mode.',
409
+ ],
410
+ tip: 'Vesk statement mode accepts bare JSX, if/for/while/switch/try without wrapping braces — no `{ }` needed around statements.',
411
+ });
412
+ }
413
+ /**
414
+ * An opening JSX tag was never closed, e.g. `<div><span>hello</div>`.
415
+ * Acorn reports this as "Expected corresponding JSX closing tag for <span>"
416
+ * or, when wrapped in a component body, as a generic "Unexpected token".
417
+ */
418
+ static jsxUnclosedTag(tagName, context = {}) {
419
+ const closing = context.closingTag ?? `</${tagName}>`;
420
+ return new VeskError(`Unclosed JSX tag \`<${tagName}>\` — expected \`${closing}\` before the closing tag.`, {
421
+ ...context,
422
+ code: context.code || 'V0420',
423
+ suggestions: [
424
+ `Add \`${closing}\` to close the \`<${tagName}>\` tag.`,
425
+ `Every opening JSX tag must have a matching closing tag: \`<${tagName}></${tagName}>\`.`,
426
+ ],
427
+ nextSteps: [
428
+ context.suggestedFix ?? `Insert \`${closing}\` right before the tag that closes the parent element.`,
429
+ `Check that all JSX elements in this component are properly closed and nested.`,
430
+ ],
431
+ tip: `The \`<${tagName}>\` tag on the marked line was opened but never closed.`,
432
+ });
433
+ }
434
+ /**
435
+ * A JSX closing tag doesn't match the most recent opening tag,
436
+ * e.g. `<div></span>`.
437
+ */
438
+ static jsxMismatchedTag(openingTag, closingTag, context = {}) {
439
+ return new VeskError(`Expected closing tag \`</${openingTag}>\` to match \`<${openingTag}>\`, but found \`</${closingTag}>\`.`, {
440
+ ...context,
441
+ code: context.code || 'V0421',
442
+ suggestions: [
443
+ `Change \`</${closingTag}>\` to \`</${openingTag}>\` to close the \`<${openingTag}>\` tag.`,
444
+ `Or change the opening tag to \`<${closingTag}>\` if that was the intended element.`,
445
+ ],
446
+ nextSteps: [
447
+ `Fix the mismatched tag on the marked line so opening and closing tags agree.`,
448
+ ],
449
+ tip: `JSX closing tags must match their opening tag: \`<${openingTag}></${openingTag}>\`, not \`<${openingTag}></${closingTag}>\`.`,
450
+ });
451
+ }
452
+ /**
453
+ * JSX content was never terminated before end of file, e.g. `<div>` with no `>`.
454
+ */
455
+ static jsxUnterminated(context = {}) {
456
+ return new VeskError('Unterminated JSX contents — the JSX block was never closed before the end of the file.', {
457
+ ...context,
458
+ code: context.code || 'V0422',
459
+ suggestions: [
460
+ 'Close the JSX element with `>` or `/>` before the end of the file.',
461
+ 'Make sure every `<` that opens a JSX tag has a matching `>` (or `/>`).',
462
+ ],
463
+ nextSteps: [
464
+ 'Add the missing `>` or `/>` to close the JSX tag.',
465
+ 'Check that no string or comment is swallowing the closing bracket.',
466
+ ],
467
+ tip: 'The JSX block on the marked line was opened but never terminated.',
468
+ });
469
+ }
470
+ /**
471
+ * An empty JSX expression container `{}`, which is not allowed.
472
+ */
473
+ static jsxEmptyExpression(context = {}) {
474
+ return new VeskError('Empty JSX expression container `{}` — JSX expressions must contain a value.', {
475
+ ...context,
476
+ code: context.code || 'V0423',
477
+ suggestions: [
478
+ 'Fill the braces with an expression: `{ count }`, `{ item.name }`, or `{ true }`.',
479
+ 'If you meant literal braces, escape them: `{ \'{\' }` and `{ \'}\' }`.',
480
+ ],
481
+ nextSteps: [
482
+ 'Replace `{}` with a real expression, or remove the empty braces.',
483
+ ],
484
+ tip: '`{}` is not valid JSX — put a value inside the braces.',
485
+ });
486
+ }
392
487
  toString() {
393
- let out = this.code ? `[vesk ${this.code}] ${this.message}` : `[vesk] ${this.message}`;
488
+ const lines = [];
489
+ // Header: `error[V####]: message` — rustc-style error code first.
490
+ lines.push(this.code ? `error[${this.code}]: ${this.message}` : `error: ${this.message}`);
491
+ // Location: `--> file:line:col`
492
+ let loc = ' --> ';
493
+ let hasLoc = false;
394
494
  if (this.file) {
395
- out += `\n File: ${this.file}`;
396
- if (this.line) {
397
- out += `:${this.line}`;
398
- if (this.column)
399
- out += `:${this.column}`;
400
- }
401
- if (this.line && this.column)
402
- out += ` (line ${this.line}, column ${this.column})`;
403
- else if (this.line)
404
- out += ` (line ${this.line})`;
495
+ loc += this.file;
496
+ hasLoc = true;
405
497
  }
406
- else if (this.line) {
407
- out += `\n at line ${this.line}${this.column ? `, column ${this.column}` : ''}`;
498
+ if (this.line) {
499
+ loc += `:${this.line}`;
500
+ hasLoc = true;
501
+ }
502
+ if (this.column) {
503
+ loc += `:${this.column}`;
504
+ hasLoc = true;
408
505
  }
409
- if (this.code)
410
- out += `\n\n${this.code}`;
411
- if (this.suggestions.length) {
412
- out += '\n\n Suggestions:';
413
- for (const s of this.suggestions.slice(0, 4))
414
- out += `\n • ${s}`;
506
+ if (hasLoc)
507
+ lines.push(loc);
508
+ // Code frame: source context with a `^` caret under the offending column.
509
+ if (this.frame) {
510
+ lines.push(' |');
511
+ for (const frameLine of this.frame.split('\n')) {
512
+ lines.push(` ${frameLine}`);
513
+ }
415
514
  }
416
- if (this.nextSteps.length) {
417
- out += '\n\n Next steps:';
418
- for (const s of this.nextSteps.slice(0, 4))
419
- out += `\n • ${s}`;
515
+ else if (this.line) {
516
+ lines.push(' |');
420
517
  }
518
+ // Help lines, rustc-style `= help:` — tip first, then next steps / suggestions.
519
+ const help = [];
421
520
  if (this.tip)
422
- out += `\n\n Tip: ${this.tip}`;
423
- return out;
521
+ help.push(this.tip);
522
+ for (const s of this.nextSteps)
523
+ help.push(s);
524
+ for (const s of this.suggestions)
525
+ help.push(s);
526
+ if (help.length) {
527
+ lines.push(' |');
528
+ for (const h of help)
529
+ lines.push(`= help: ${h}`);
530
+ }
531
+ return lines.join('\n');
424
532
  }
425
533
  toJSON() {
426
534
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAuBP,MAAM,uBAAuB,CAAC;AAkW/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAsvDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsV9E"}
1
+ {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAuBP,MAAM,uBAAuB,CAAC;AAkW/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AA0yDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsV9E"}
@@ -974,6 +974,12 @@ function processAttribute(source, attr) {
974
974
  return { name, value: '' };
975
975
  if (attr.value.type === 'Literal')
976
976
  return { name, value: String(attr.value.value) };
977
+ if (attr.value.type === 'TemplateLiteral') {
978
+ // A bare backtick template literal in a JSX attribute, e.g.
979
+ // `<Link href=`/admin/learners/${id}` />`. It is a dynamic value, so it
980
+ // becomes an attribute binding rather than a static string.
981
+ return { name, value: toExpression(source, attr.value) };
982
+ }
977
983
  if (attr.value.type === 'JSXExpressionContainer') {
978
984
  const expr = attr.value.expression;
979
985
  if (expr.type === 'Literal')
@@ -1596,6 +1602,11 @@ function extractProps(source, element) {
1596
1602
  }
1597
1603
  value = toExpression(source, expr);
1598
1604
  }
1605
+ else if (attr.value.type === 'TemplateLiteral') {
1606
+ // A bare backtick template literal prop value, e.g.
1607
+ // `<Comp href=`/admin/learners/${id}` />`.
1608
+ value = toExpression(source, attr.value);
1609
+ }
1599
1610
  else {
1600
1611
  value = new Expression(JSON.stringify(attr.value.value));
1601
1612
  }
@@ -1765,6 +1776,8 @@ function isStatementMode(bodyStmts) {
1765
1776
  if (bodyStmts.some((s) => s.type === 'JSXElement' || s.type === 'JSXExpressionContainer' || s.type === 'JSXFragment'))
1766
1777
  return true;
1767
1778
  for (const stmt of bodyStmts) {
1779
+ if (stmt.type === 'BlockStatement' && hasJSXInSubtree(stmt))
1780
+ return true;
1768
1781
  if (stmt.type === 'IfStatement' && !isGuardClause(stmt) && hasJSXInSubtree(stmt))
1769
1782
  return true;
1770
1783
  if (stmt.type === 'ForOfStatement' && hasJSXInSubtree(stmt))
@@ -1928,6 +1941,57 @@ function processStatementModeBody(source, bodyStmts, filename) {
1928
1941
  if (refName)
1929
1942
  nodes.push(new ComponentRef(refName));
1930
1943
  }
1944
+ else if (stmt.type === 'VariableDeclaration') {
1945
+ // Plain variable declarations (`const items = [1, 2, 3]`, `let x = 1`)
1946
+ // that are not track() declarations are regular JavaScript. Emit them
1947
+ // verbatim as runtime statements so they execute in statement mode.
1948
+ const raw = getSource(source, stmt);
1949
+ if (raw)
1950
+ nodes.push(new RuntimeStatement(raw, stmt, source));
1951
+ }
1952
+ else if (stmt.type === 'BlockStatement') {
1953
+ // Braces around statements are unnecessary in Vesk statement mode —
1954
+ // `if (cond) { <jsx/> }` is written without the outer `{ }`. If the
1955
+ // block contains only JSX children, unwrap them; otherwise give a
1956
+ // clear error (braces around `if`/`for`/`const`/etc. are not needed).
1957
+ const innerStmts = stmt.body || [];
1958
+ const hasNonJSX = innerStmts.some((s) => s.type !== 'JSXElement' && s.type !== 'JSXExpressionContainer' && s.type !== 'JSXFragment');
1959
+ if (hasNonJSX) {
1960
+ const start = stmt.start ?? 0;
1961
+ let line = 1;
1962
+ let column = 1;
1963
+ for (let i = 0; i < start && i < source.length; i++) {
1964
+ if (source.charCodeAt(i) === 10) {
1965
+ line++;
1966
+ column = 1;
1967
+ }
1968
+ else {
1969
+ column++;
1970
+ }
1971
+ }
1972
+ throw VeskError.blockStatementInStatementMode({
1973
+ file: filename || '',
1974
+ line,
1975
+ column,
1976
+ code: codeFrame(source, line, column),
1977
+ });
1978
+ }
1979
+ // Pure JSX block: unwrap the children directly.
1980
+ for (const child of innerStmts) {
1981
+ if (child.type === 'JSXElement') {
1982
+ nodes.push(...processJSXElement(source, child));
1983
+ }
1984
+ else if (child.type === 'JSXExpressionContainer') {
1985
+ if (child.expression.type === 'JSXEmptyExpression')
1986
+ continue;
1987
+ nodes.push(...exprToIR(source, child.expression));
1988
+ }
1989
+ else if (child.type === 'JSXFragment') {
1990
+ for (const c of child.children)
1991
+ nodes.push(...processJSXChildren(source, [c]));
1992
+ }
1993
+ }
1994
+ }
1931
1995
  else if (stmt.type === 'IfStatement') {
1932
1996
  // Guard-clause early return (`if (c) return X` with no else): everything
1933
1997
  // after this statement becomes the alternate branch, mirroring
@@ -2388,7 +2452,7 @@ export function generateIR(ast, source, filename) {
2388
2452
  for (const spec of node.specifiers) {
2389
2453
  if (spec.importKind === 'type')
2390
2454
  continue;
2391
- if (spec.type === 'ImportSpecifier') {
2455
+ if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
2392
2456
  importedNames.add(spec.local.name);
2393
2457
  }
2394
2458
  }