@vesk/compiler 0.2.93 → 0.2.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +315 -57
- package/dist/ir.d.ts +6 -0
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +15 -0
- package/dist/module-imports.d.ts +9 -2
- package/dist/module-imports.d.ts.map +1 -1
- package/dist/module-imports.js +163 -38
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +40 -2
- package/dist/router.d.ts +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +17 -2
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +14 -2
- package/dist/typecheck.d.ts +1 -1
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +5 -9
- package/package.json +2 -2
package/dist/client-codegen.js
CHANGED
|
@@ -2,7 +2,7 @@ import { walk } from 'zimmerframe';
|
|
|
2
2
|
import { print } from 'esrap';
|
|
3
3
|
import ts from 'esrap/languages/ts';
|
|
4
4
|
import tsx from 'esrap/languages/tsx';
|
|
5
|
-
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, BreakSignal, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
|
|
5
|
+
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, BreakSignal, ComponentRef, ComponentCall, regionMarkerId, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
|
|
6
6
|
import { parse } from '@vesk/compiler/src/parser';
|
|
7
7
|
import { generateIR } from '@vesk/compiler/src/ir-generator';
|
|
8
8
|
import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
|
|
@@ -535,6 +535,10 @@ class Ctx {
|
|
|
535
535
|
markerless = true;
|
|
536
536
|
inTryBody = false;
|
|
537
537
|
claimStatic = false;
|
|
538
|
+
// Markerless only: the next real claim carries a compiler-generated static
|
|
539
|
+
// class descriptor so a same-tag but structurally different SSR slot is
|
|
540
|
+
// substituted instead of adopted (e.g. an empty-state <div> vs a list <div>).
|
|
541
|
+
descriptorClaim = false;
|
|
538
542
|
// While set, the next StaticNode claimed in hydrate mode uses `__root` (the
|
|
539
543
|
// item element already claimed by key) instead of walking for a new element.
|
|
540
544
|
rootClaim = false;
|
|
@@ -685,6 +689,7 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
|
|
|
685
689
|
ctx.push(` const __slot = createLayoutSlot(${ctx.walker}, ${parentVar});`);
|
|
686
690
|
ctx.push(` const __child = props.children(__slot.walker);`);
|
|
687
691
|
ctx.push(` if (__child && typeof __child.then === 'function') __pendingChild = __slot.track(__child);`);
|
|
692
|
+
ctx.push(` else if (__child && __child.nodeType === 11) ${parentVar}.appendChild(__child);`);
|
|
688
693
|
ctx.push(` } else {`);
|
|
689
694
|
ctx.push(` ${parentVar}.appendChild(props.children);`);
|
|
690
695
|
ctx.push(` }`);
|
|
@@ -738,6 +743,24 @@ const PROPERTY_ATTRS = {
|
|
|
738
743
|
option: new Set(['selected']),
|
|
739
744
|
progress: new Set(['value']),
|
|
740
745
|
};
|
|
746
|
+
/**
|
|
747
|
+
* Build the markerless structural descriptor for a claimed element. Only a
|
|
748
|
+
* source-authored static class is included: dynamic classes are runtime values
|
|
749
|
+
* and must never be validated (SSR/client first paint may legitimately differ).
|
|
750
|
+
* The descriptor is a JS-only argument to the walker; it never reaches SSR HTML.
|
|
751
|
+
*/
|
|
752
|
+
function staticClassDescriptor(node) {
|
|
753
|
+
const hasDynamicClass = node.children.some((child) => child instanceof DynamicBinding && child.kind === 'attribute' && child.target === 'class');
|
|
754
|
+
if (hasDynamicClass)
|
|
755
|
+
return null;
|
|
756
|
+
const attr = node.attributes.find((a) => a.name === 'class');
|
|
757
|
+
if (!attr)
|
|
758
|
+
return null;
|
|
759
|
+
const value = attr.value.trim();
|
|
760
|
+
if (!value)
|
|
761
|
+
return null;
|
|
762
|
+
return JSON.stringify({ class: value });
|
|
763
|
+
}
|
|
741
764
|
function emitStatic(ctx, node, tracked, effectsVar, skipK = 0, regionBudget) {
|
|
742
765
|
const el = ctx.n();
|
|
743
766
|
if (ctx.hydrate) {
|
|
@@ -761,13 +784,21 @@ function emitStatic(ctx, node, tracked, effectsVar, skipK = 0, regionBudget) {
|
|
|
761
784
|
// are claimed on their own sub-walker and carry their own residue math.
|
|
762
785
|
const skipExpr = regionBudget && ctx.markerless ? markerlessSkipExpr(regionBudget, skipK) : skipK;
|
|
763
786
|
const skipArg = ctx.markerless ? `, ${skipExpr}` : '';
|
|
787
|
+
// A component root or region branch root may be the slot where the client's
|
|
788
|
+
// initial branch differs from SSR. Validate its static class so a same-tag
|
|
789
|
+
// wrong branch is substituted, not adopted. One-shot: only the first real
|
|
790
|
+
// claim in that root/branch carries the descriptor.
|
|
791
|
+
const wantsDescriptor = ctx.markerless && ctx.descriptorClaim;
|
|
792
|
+
ctx.descriptorClaim = false;
|
|
793
|
+
const descriptor = wantsDescriptor && !rootClaim ? staticClassDescriptor(node) : null;
|
|
794
|
+
const descriptorArg = descriptor ? `, ${descriptor}` : '';
|
|
764
795
|
// `rootClaim` marks the keyed renderItem root: claimed items supply the
|
|
765
796
|
// SSR element as `__root`, fresh items must CREATE one. The walker is never
|
|
766
797
|
// valid here as a fallback — its cursor doesn't advance past claimByKey, so
|
|
767
798
|
// `nextElement` would steal a neighboring SSR node to render the new item.
|
|
768
799
|
const claimedExpr = rootClaim
|
|
769
800
|
? `__root || document.createElement(${JSON.stringify(node.tag)})`
|
|
770
|
-
: `${ctx.walker}.nextElement(${JSON.stringify(node.tag)}${skipArg})`;
|
|
801
|
+
: `${ctx.walker}.nextElement(${JSON.stringify(node.tag)}${skipArg}${descriptorArg})`;
|
|
771
802
|
ctx.push(`const ${el} = ${claimedExpr};`);
|
|
772
803
|
if (regionBudget && ctx.markerless)
|
|
773
804
|
ctx.push(`${regionBudget} = 0;`);
|
|
@@ -1269,6 +1300,65 @@ function emitFragment(ctx, nodes, tracked, effectsVar) {
|
|
|
1269
1300
|
ctx.push(`return $f; })();`);
|
|
1270
1301
|
return frag;
|
|
1271
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* True when any node in the subtree is a layout `{props.children}` slot.
|
|
1305
|
+
*
|
|
1306
|
+
* A slot must be hydrated on the walker scoped to the element that will
|
|
1307
|
+
* actually contain it. Building the children fragment eagerly on the caller's
|
|
1308
|
+
* walker is only correct when the callee appends that fragment to its own
|
|
1309
|
+
* mount point. A component that forwards children through a scoped container
|
|
1310
|
+
* (e.g. a shell that renders `{props.children}` inside `<main>`) invokes the
|
|
1311
|
+
* child hydrator with a sub-walker; passing an eagerly-built fragment skips
|
|
1312
|
+
* that scope and lets the page claim the shell's own SSR root.
|
|
1313
|
+
*/
|
|
1314
|
+
function containsSlot(nodes) {
|
|
1315
|
+
for (const node of nodes) {
|
|
1316
|
+
if (node instanceof SlotNode)
|
|
1317
|
+
return true;
|
|
1318
|
+
if (node instanceof StaticNode) {
|
|
1319
|
+
if (containsSlot(node.children))
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
else if (node instanceof ComponentCall) {
|
|
1323
|
+
if (containsSlot(node.children))
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
else if (node instanceof OpaqueDynamicRegion) {
|
|
1327
|
+
if (containsSlot(node.consequentNodes) || containsSlot(node.alternateNodes))
|
|
1328
|
+
return true;
|
|
1329
|
+
}
|
|
1330
|
+
else if (node instanceof MapRegion) {
|
|
1331
|
+
if (containsSlot(node.bodyTemplate) || containsSlot(node.alternateNodes))
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
else if (node instanceof WhileLoop) {
|
|
1335
|
+
if (containsSlot(node.bodyTemplate))
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
else if (node instanceof ForLoop) {
|
|
1339
|
+
if (containsSlot(node.bodyTemplate))
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
else if (node instanceof SwitchBlock) {
|
|
1343
|
+
for (const c of node.cases)
|
|
1344
|
+
if (containsSlot(c.body))
|
|
1345
|
+
return true;
|
|
1346
|
+
}
|
|
1347
|
+
else if (node instanceof TryCatch) {
|
|
1348
|
+
if (containsSlot(node.bodyTemplate) || containsSlot(node.catchBody))
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
else if (node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
|
|
1352
|
+
if (containsSlot(node.children))
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1355
|
+
else if (node instanceof PropSlot) {
|
|
1356
|
+
if (containsSlot(node.body))
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1272
1362
|
function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__components', skipK = 0, regionBudget) {
|
|
1273
1363
|
const propsEntries = node.props.map((p) => {
|
|
1274
1364
|
if (typeof p.value === 'string')
|
|
@@ -1328,6 +1418,11 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
1328
1418
|
? `reactiveProps({ ${propsEntries.join(', ')} })`
|
|
1329
1419
|
: `{ ${propsEntries.join(', ')} }`);
|
|
1330
1420
|
if (ctx.hydrate) {
|
|
1421
|
+
// A component call is itself the root claim site. A compiled child carries
|
|
1422
|
+
// its own root descriptor; a plain target has no IR root to describe. In
|
|
1423
|
+
// either case the caller's pending branch descriptor must not leak to a
|
|
1424
|
+
// following sibling.
|
|
1425
|
+
ctx.descriptorClaim = false;
|
|
1331
1426
|
const access = calleeExpr
|
|
1332
1427
|
?? (ctx.importedNames.has(node.componentName)
|
|
1333
1428
|
? node.componentName
|
|
@@ -1406,20 +1501,56 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
|
|
|
1406
1501
|
// fresh and let the runtime component adopt the existing root.
|
|
1407
1502
|
if (wipeMount || ctx.selfClaimNames.has(node.componentName))
|
|
1408
1503
|
ctx.hydrate = false;
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1504
|
+
// A slot nested under a compiled component must be built by that
|
|
1505
|
+
// component's scoped walker, not the caller's. Pass children as a lazy
|
|
1506
|
+
// hydrator so the callee can invoke it with the walker for the element
|
|
1507
|
+
// that actually contains the slot (e.g. `<main>` in a shell). Eagerly
|
|
1508
|
+
// building the fragment here hydrates the page against the shell's own
|
|
1509
|
+
// SSR root, which detaches the shell on hydration.
|
|
1510
|
+
const lazySlotChildren = ctx.hydrate && !plainTarget && !ctx.selfClaimNames.has(node.componentName) && containsSlot(regChildren);
|
|
1511
|
+
if (lazySlotChildren) {
|
|
1512
|
+
const fnVar = ctx.n();
|
|
1513
|
+
const savedLines = ctx.lines;
|
|
1514
|
+
const savedEffects = ctx.effects;
|
|
1515
|
+
const savedWalker = ctx.walker;
|
|
1516
|
+
ctx.lines = [];
|
|
1517
|
+
ctx.effects = [];
|
|
1518
|
+
ctx.walker = '__childWalker';
|
|
1519
|
+
ctx.push(`const $f = document.createDocumentFragment();`);
|
|
1520
|
+
for (const child of regChildren) {
|
|
1521
|
+
const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
|
|
1522
|
+
if (childVar)
|
|
1523
|
+
ctx.push(`$f.appendChild(${childVar});`);
|
|
1524
|
+
}
|
|
1525
|
+
for (const eff of ctx.effects)
|
|
1526
|
+
ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
|
|
1527
|
+
ctx.push(`return $f;`);
|
|
1528
|
+
const body = ctx.lines.join('\n');
|
|
1529
|
+
ctx.lines = savedLines;
|
|
1530
|
+
ctx.effects = savedEffects;
|
|
1531
|
+
ctx.walker = savedWalker;
|
|
1532
|
+
const asyncKw = ctx.isAsyncScope ? 'async ' : '';
|
|
1533
|
+
ctx.push(`const ${fnVar} = ${asyncKw}(__childWalker) => {`);
|
|
1534
|
+
ctx.push(indent(body));
|
|
1535
|
+
ctx.push(`};`);
|
|
1536
|
+
propsEntries.push(`children: ${fnVar}`);
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
ctx.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
|
|
1540
|
+
const savedEffects = ctx.effects;
|
|
1541
|
+
ctx.effects = [];
|
|
1542
|
+
for (const child of regChildren) {
|
|
1543
|
+
const childVar = emitNode(ctx, child, tracked, effectsVar, '$f');
|
|
1544
|
+
if (childVar)
|
|
1545
|
+
ctx.push(`$f.appendChild(${childVar});`);
|
|
1546
|
+
}
|
|
1547
|
+
for (const eff of ctx.effects)
|
|
1548
|
+
ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
|
|
1549
|
+
ctx.effects = savedEffects;
|
|
1550
|
+
ctx.push(`return $f; })();`);
|
|
1551
|
+
propsEntries.push(`children: ${frag}`);
|
|
1416
1552
|
}
|
|
1417
|
-
for (const eff of ctx.effects)
|
|
1418
|
-
ctx.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
|
|
1419
|
-
ctx.effects = savedEffects;
|
|
1420
1553
|
ctx.hydrate = savedHydrate;
|
|
1421
|
-
ctx.push(`return $f; })();`);
|
|
1422
|
-
propsEntries.push(`children: ${frag}`);
|
|
1423
1554
|
}
|
|
1424
1555
|
// Deposit the residue for the child's first top-level claim (it runs on
|
|
1425
1556
|
// this same walker with its own zero `topPendingSkip`). A region budget is
|
|
@@ -1667,7 +1798,7 @@ function emitTryCatch(ctx, node, tracked, effectsVar, parentVar, skipK = 0, regi
|
|
|
1667
1798
|
${effArr}.length = 0;
|
|
1668
1799
|
__cleanup(${anchor}, ${endAnchor});
|
|
1669
1800
|
try { ${asyncKw}${tryRenderComp}(); } catch(${catchParam}) { ${hasCatch ? `${asyncKw}${catchRenderComp}(${catchParam});` : 'throw ${catchParam};'} }
|
|
1670
|
-
});
|
|
1801
|
+
}, true);
|
|
1671
1802
|
}`);
|
|
1672
1803
|
return null;
|
|
1673
1804
|
}
|
|
@@ -1675,13 +1806,14 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1675
1806
|
const hyd = ctx.hydrate;
|
|
1676
1807
|
const parent = parentVar || '$root';
|
|
1677
1808
|
const budget = hyd ? regionBudgetVar(ctx, skipK, regionBudget) : null;
|
|
1809
|
+
const rid = regionMarkerId(node);
|
|
1678
1810
|
const isOpaque = (n) => !!n && typeof n.condition !== 'undefined';
|
|
1679
1811
|
if (hyd && node.alternateNodes.length === 1 && isOpaque(node.alternateNodes[0])) {
|
|
1680
1812
|
const chain = [];
|
|
1681
1813
|
let cur = node;
|
|
1682
1814
|
let finalElse = null;
|
|
1683
1815
|
while (cur) {
|
|
1684
|
-
chain.push({ cond: transformTracked(cur.condition, tracked), nodes: cur.consequentNodes });
|
|
1816
|
+
chain.push({ cond: transformTracked(cur.condition, tracked), nodes: cur.consequentNodes, id: regionMarkerId(cur) });
|
|
1685
1817
|
if (cur.alternateNodes.length === 1 && isOpaque(cur.alternateNodes[0])) {
|
|
1686
1818
|
cur = cur.alternateNodes[0];
|
|
1687
1819
|
}
|
|
@@ -1706,6 +1838,9 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1706
1838
|
ctx.push(`const ${rn} = ${fnOpen}`);
|
|
1707
1839
|
const cl = ctx.n();
|
|
1708
1840
|
ctx.push(indent(`const ${cl} = [];`));
|
|
1841
|
+
const savedDescriptor = ctx.descriptorClaim;
|
|
1842
|
+
if (hyd && ctx.markerless)
|
|
1843
|
+
ctx.descriptorClaim = true;
|
|
1709
1844
|
for (const n of chain[i].nodes) {
|
|
1710
1845
|
const savedClaim = ctx.claimStatic;
|
|
1711
1846
|
if (hyd)
|
|
@@ -1715,6 +1850,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1715
1850
|
if (v)
|
|
1716
1851
|
ctx.push(indent(`${cl}.push(${v});`));
|
|
1717
1852
|
}
|
|
1853
|
+
ctx.descriptorClaim = savedDescriptor;
|
|
1718
1854
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, ${cl}, ${parent});`));
|
|
1719
1855
|
ctx.push(`};`);
|
|
1720
1856
|
}
|
|
@@ -1724,6 +1860,9 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1724
1860
|
ctx.push(`const ${finalRender} = ${fnOpen}`);
|
|
1725
1861
|
const cl2 = ctx.n();
|
|
1726
1862
|
ctx.push(indent(`const ${cl2} = [];`));
|
|
1863
|
+
const savedDescriptor = ctx.descriptorClaim;
|
|
1864
|
+
if (hyd && ctx.markerless)
|
|
1865
|
+
ctx.descriptorClaim = true;
|
|
1727
1866
|
for (const n of finalElse) {
|
|
1728
1867
|
const savedClaim = ctx.claimStatic;
|
|
1729
1868
|
if (hyd)
|
|
@@ -1733,15 +1872,10 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1733
1872
|
if (v)
|
|
1734
1873
|
ctx.push(indent(`${cl2}.push(${v});`));
|
|
1735
1874
|
}
|
|
1875
|
+
ctx.descriptorClaim = savedDescriptor;
|
|
1736
1876
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, ${cl2}, ${parent});`));
|
|
1737
1877
|
ctx.push(`};`);
|
|
1738
1878
|
}
|
|
1739
|
-
let init = `if (${chain[0].cond}) { ${asyncKw}${renderNames[0]}(); }`;
|
|
1740
|
-
for (let i = 1; i < chain.length; i++)
|
|
1741
|
-
init += ` else if (${chain[i].cond}) { ${asyncKw}${renderNames[i]}(); }`;
|
|
1742
|
-
if (finalRender)
|
|
1743
|
-
init += ` else { ${asyncKw}${finalRender}(); }`;
|
|
1744
|
-
ctx.push(init);
|
|
1745
1879
|
// Resolve which branch index the chain is in, for any chain depth. The last
|
|
1746
1880
|
// fallback is `chain.length` when a final `else` exists, otherwise -1 (no
|
|
1747
1881
|
// branch renders). Build the nested ternary from the innermost condition
|
|
@@ -1751,13 +1885,37 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1751
1885
|
branchInitFallback = `(${chain[i].cond} ? ${i} : ${branchInitFallback})`;
|
|
1752
1886
|
}
|
|
1753
1887
|
const branchInit = `(${branchInitFallback})`;
|
|
1888
|
+
// Snapshot the branch chosen by the INITIAL render before claiming it. The
|
|
1889
|
+
// effect flush runs after the component body, so a tracked cell can change
|
|
1890
|
+
// in between (bootstrap hydration, a child island's body, an inline
|
|
1891
|
+
// statement). Initializing `__iv` at flush time would silently adopt the
|
|
1892
|
+
// new branch while the DOM still held the old one; the first effect run
|
|
1893
|
+
// must compare against the branch that actually rendered.
|
|
1894
|
+
const branchSnapshot = ctx.n();
|
|
1895
|
+
ctx.push(`const ${branchSnapshot} = ${branchInit};`);
|
|
1896
|
+
if (!ctx.markerless) {
|
|
1897
|
+
for (let i = 0; i < chain.length; i++) {
|
|
1898
|
+
ctx.push(`if (__hydrate) __reconcileRegion(${parent}, '${chain[i].id}', ${branchSnapshot} === ${i} ? 0 : 1, ${ctx.walker});`);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
else {
|
|
1902
|
+
const chainBranches = chain.map((c) => c.nodes);
|
|
1903
|
+
if (finalElse)
|
|
1904
|
+
chainBranches.push(finalElse);
|
|
1905
|
+
emitStructuralRegionDiscard(ctx, chainBranches, branchSnapshot, budget);
|
|
1906
|
+
}
|
|
1907
|
+
let init = `if (${branchSnapshot} === 0) { ${asyncKw}${renderNames[0]}(); }`;
|
|
1908
|
+
for (let i = 1; i < chain.length; i++)
|
|
1909
|
+
init += ` else if (${branchSnapshot} === ${i}) { ${asyncKw}${renderNames[i]}(); }`;
|
|
1910
|
+
if (finalRender)
|
|
1911
|
+
init += ` else { ${asyncKw}${finalRender}(); }`;
|
|
1912
|
+
ctx.push(init);
|
|
1754
1913
|
let effInner = `if (__new === 0) { ${asyncKw}${renderNames[0]}(); }`;
|
|
1755
1914
|
for (let i = 1; i < chain.length; i++)
|
|
1756
1915
|
effInner += ` else if (__new === ${i}) { ${asyncKw}${renderNames[i]}(); }`;
|
|
1757
1916
|
if (finalRender)
|
|
1758
1917
|
effInner += ` else { ${asyncKw}${finalRender}(); }`;
|
|
1759
1918
|
const effBody = `effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
1760
|
-
if (__first) { __first = false; __iv = ${branchInit}; return; }
|
|
1761
1919
|
const __new = ${branchInit};
|
|
1762
1920
|
if (__new !== __iv) {
|
|
1763
1921
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
@@ -1766,18 +1924,16 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1766
1924
|
${effInner}
|
|
1767
1925
|
__iv = __new;
|
|
1768
1926
|
}
|
|
1769
|
-
});`;
|
|
1927
|
+
}, true);`;
|
|
1770
1928
|
if (effTarget) {
|
|
1771
1929
|
ctx.push(`${effTarget}.push((() => {
|
|
1772
|
-
let __iv = ${
|
|
1773
|
-
let __first = true;
|
|
1930
|
+
let __iv = ${branchSnapshot};
|
|
1774
1931
|
return ${effBody}
|
|
1775
1932
|
})());`);
|
|
1776
1933
|
}
|
|
1777
1934
|
else {
|
|
1778
1935
|
ctx.effects.push(`{
|
|
1779
|
-
let __iv = ${
|
|
1780
|
-
let __first = true;
|
|
1936
|
+
let __iv = ${branchSnapshot};
|
|
1781
1937
|
${effBody}
|
|
1782
1938
|
}`);
|
|
1783
1939
|
}
|
|
@@ -1805,6 +1961,9 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1805
1961
|
ctx.push(indent(`const ${conFrag} = document.createDocumentFragment();`));
|
|
1806
1962
|
if (hyd)
|
|
1807
1963
|
ctx.push(indent(`const __cl = [];`));
|
|
1964
|
+
const savedConDescriptor = ctx.descriptorClaim;
|
|
1965
|
+
if (hyd && ctx.markerless)
|
|
1966
|
+
ctx.descriptorClaim = true;
|
|
1808
1967
|
for (const n of node.consequentNodes) {
|
|
1809
1968
|
const savedClaim = ctx.claimStatic;
|
|
1810
1969
|
if (hyd)
|
|
@@ -1818,6 +1977,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1818
1977
|
ctx.push(indent(`${conFrag}.appendChild(${v});`));
|
|
1819
1978
|
}
|
|
1820
1979
|
}
|
|
1980
|
+
ctx.descriptorClaim = savedConDescriptor;
|
|
1821
1981
|
if (hyd)
|
|
1822
1982
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, __cl, ${parent});`));
|
|
1823
1983
|
else
|
|
@@ -1834,6 +1994,9 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1834
1994
|
ctx.push(indent(`const ${altFrag} = document.createDocumentFragment();`));
|
|
1835
1995
|
if (hyd)
|
|
1836
1996
|
ctx.push(indent(`const __cl = [];`));
|
|
1997
|
+
const savedAltDescriptor = ctx.descriptorClaim;
|
|
1998
|
+
if (hyd && ctx.markerless)
|
|
1999
|
+
ctx.descriptorClaim = true;
|
|
1837
2000
|
for (const n of node.alternateNodes) {
|
|
1838
2001
|
const savedAltClaim = ctx.claimStatic;
|
|
1839
2002
|
if (hyd)
|
|
@@ -1847,6 +2010,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1847
2010
|
ctx.push(indent(`${altFrag}.appendChild(${v});`));
|
|
1848
2011
|
}
|
|
1849
2012
|
}
|
|
2013
|
+
ctx.descriptorClaim = savedAltDescriptor;
|
|
1850
2014
|
if (hyd)
|
|
1851
2015
|
ctx.push(indent(`__place(${anchor}, ${endAnchor}, __cl, ${parent});`));
|
|
1852
2016
|
else
|
|
@@ -1855,10 +2019,19 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1855
2019
|
}
|
|
1856
2020
|
if (!hyd)
|
|
1857
2021
|
ctx.push(`${parent}.appendChild(${endAnchor});`);
|
|
1858
|
-
|
|
2022
|
+
// Snapshot the condition used by the INITIAL render. Effect flush runs after
|
|
2023
|
+
// the body, so tracked data can arrive in between; comparing the first effect
|
|
2024
|
+
// run against a value read at flush time would adopt the new branch without
|
|
2025
|
+
// replacing the DOM that the old branch already claimed.
|
|
2026
|
+
const condSnapshot = ctx.n();
|
|
2027
|
+
ctx.push(`const ${condSnapshot} = ${condExpr};`);
|
|
2028
|
+
if (hyd && !ctx.markerless)
|
|
2029
|
+
ctx.push(`if (__hydrate) __reconcileRegion(${parent}, '${rid}', ${condSnapshot} ? 0 : 1, ${ctx.walker});`);
|
|
2030
|
+
if (hyd && ctx.markerless)
|
|
2031
|
+
emitStructuralRegionDiscard(ctx, [node.consequentNodes, node.alternateNodes], hasElse ? `(${condSnapshot} ? 0 : 1)` : `(${condSnapshot} ? 0 : -1)`, budget);
|
|
2032
|
+
ctx.push(`if (${condSnapshot}) { ${asyncKw}${conRenderName}(); }` + (hasElse ? ` else { ${asyncKw}${altRenderName}(); }` : ''));
|
|
1859
2033
|
const renderBranch = `if (__nv) { ${asyncKw}${conRenderName}(); }` + (hasElse ? ` else { ${asyncKw}${altRenderName}(); }` : '');
|
|
1860
2034
|
const effectBody = `effect(${ctx.isAsyncScope ? 'async () => {' : '() => {'}
|
|
1861
|
-
if (__first) { __first = false; __iv = ${condExpr}; return; }
|
|
1862
2035
|
const __nv = ${condExpr};
|
|
1863
2036
|
if (__nv !== __iv) {
|
|
1864
2037
|
for (const e of ${effectsVar}) destroy_block(e);
|
|
@@ -1867,7 +2040,7 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1867
2040
|
${renderBranch}
|
|
1868
2041
|
__iv = __nv;
|
|
1869
2042
|
}
|
|
1870
|
-
});`;
|
|
2043
|
+
}, true);`;
|
|
1871
2044
|
// A static condition renders once; an effect referencing it would only ever
|
|
1872
2045
|
// reference statement/loop-local bindings that are out of scope at the flush
|
|
1873
2046
|
// site (e.g. `const pages = …; if (pages.length > 0)` inside a for-of body).
|
|
@@ -1875,15 +2048,13 @@ function emitOpaque(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionB
|
|
|
1875
2048
|
return null;
|
|
1876
2049
|
if (effTarget) {
|
|
1877
2050
|
ctx.push(`${effTarget}.push((() => {
|
|
1878
|
-
let __iv = ${
|
|
1879
|
-
let __first = true;
|
|
2051
|
+
let __iv = ${condSnapshot};
|
|
1880
2052
|
return ${effectBody}
|
|
1881
2053
|
})());`);
|
|
1882
2054
|
}
|
|
1883
2055
|
else {
|
|
1884
2056
|
ctx.effects.push(`{
|
|
1885
|
-
let __iv = ${
|
|
1886
|
-
let __first = true;
|
|
2057
|
+
let __iv = ${condSnapshot};
|
|
1887
2058
|
${effectBody}
|
|
1888
2059
|
}`);
|
|
1889
2060
|
}
|
|
@@ -1973,7 +2144,7 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
|
|
|
1973
2144
|
__busy = false;
|
|
1974
2145
|
__iv = (${condExpr});
|
|
1975
2146
|
}
|
|
1976
|
-
});
|
|
2147
|
+
}, true);
|
|
1977
2148
|
}`);
|
|
1978
2149
|
return null;
|
|
1979
2150
|
}
|
|
@@ -2087,7 +2258,7 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
|
|
|
2087
2258
|
__busy = false;
|
|
2088
2259
|
__iv = (${srcExpr});
|
|
2089
2260
|
}
|
|
2090
|
-
});
|
|
2261
|
+
}, true);
|
|
2091
2262
|
}`);
|
|
2092
2263
|
}
|
|
2093
2264
|
else {
|
|
@@ -2156,7 +2327,7 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
|
|
|
2156
2327
|
__cleanup(${anchor}, ${endAnchor});
|
|
2157
2328
|
${ctx.isAsyncScope ? 'await ' : ''}${renderLoop}();
|
|
2158
2329
|
__busy = false;
|
|
2159
|
-
});
|
|
2330
|
+
}, true);
|
|
2160
2331
|
}`);
|
|
2161
2332
|
}
|
|
2162
2333
|
}
|
|
@@ -2233,7 +2404,7 @@ function emitSwitchBlock(ctx, node, tracked, parentVar, skipK = 0, regionBudget)
|
|
|
2233
2404
|
__busy = false;
|
|
2234
2405
|
__iv = (${discExpr});
|
|
2235
2406
|
}
|
|
2236
|
-
});
|
|
2407
|
+
}, true);
|
|
2237
2408
|
}`);
|
|
2238
2409
|
return null;
|
|
2239
2410
|
}
|
|
@@ -2380,34 +2551,34 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
|
|
|
2380
2551
|
let emptyRenderName = null;
|
|
2381
2552
|
if (node.alternateNodes.length > 0) {
|
|
2382
2553
|
emptyRenderName = ctx.n();
|
|
2383
|
-
|
|
2554
|
+
// Hydrated maps (keyed AND non-keyed) claim their #empty fallback
|
|
2555
|
+
// positionally with descriptor checks: an SSR slot that rendered the same
|
|
2556
|
+
// fallback is adopted in place (no duplicate), while an SSR item ghost in
|
|
2557
|
+
// that slot is detached by the walker's mismatch recovery and the fallback
|
|
2558
|
+
// renders fresh. Fresh (SPA) renders keep the fragment + direct-insert
|
|
2559
|
+
// shape — there is no SSR DOM to claim.
|
|
2560
|
+
const emptyClaims = ctx.hydrate;
|
|
2561
|
+
const emptySig = !emptyClaims
|
|
2384
2562
|
? (ctx.isAsyncScope ? 'async () => {' : '() => {')
|
|
2385
2563
|
: (ctx.isAsyncScope ? 'async (__cl) => {' : '(__cl) => {');
|
|
2386
2564
|
ctx.push(`const ${emptyRenderName} = ${emptySig}`);
|
|
2387
2565
|
ctx.push(indent(`const __p = ${anchor}.parentNode;`));
|
|
2388
2566
|
const frag = ctx.n();
|
|
2389
|
-
if (!
|
|
2567
|
+
if (!emptyClaims)
|
|
2390
2568
|
ctx.push(indent(`const ${frag} = document.createDocumentFragment();`));
|
|
2391
2569
|
const savedClaim = ctx.claimStatic;
|
|
2392
|
-
const savedHydrate = ctx.hydrate;
|
|
2393
|
-
// A keyed map hydrating with no matching SSR items renders its empty
|
|
2394
|
-
// fallback fresh — the SSR item ghosts get reported by the canary instead
|
|
2395
|
-
// of being adopted positionally as the fallback (which would be wrong DOM).
|
|
2396
|
-
if (hydKeyed)
|
|
2397
|
-
ctx.hydrate = false;
|
|
2398
2570
|
for (const n of node.alternateNodes) {
|
|
2399
2571
|
ctx.claimStatic = true;
|
|
2400
|
-
const v = emitNode(ctx, n, tracked, effectsVar,
|
|
2572
|
+
const v = emitNode(ctx, n, tracked, effectsVar, emptyClaims ? undefined : frag, '__components', 0, budget || undefined);
|
|
2401
2573
|
if (v) {
|
|
2402
|
-
if (
|
|
2574
|
+
if (emptyClaims)
|
|
2403
2575
|
ctx.push(indent(`if (__cl) __cl.push(${v}); else __p.insertBefore(${v}, ${endAnchor});`));
|
|
2404
2576
|
else
|
|
2405
2577
|
ctx.push(indent(`${frag}.appendChild(${v});`));
|
|
2406
2578
|
}
|
|
2407
2579
|
}
|
|
2408
2580
|
ctx.claimStatic = savedClaim;
|
|
2409
|
-
|
|
2410
|
-
if (!hyd)
|
|
2581
|
+
if (!emptyClaims)
|
|
2411
2582
|
ctx.push(indent(`if (${frag}.childNodes.length > 0) __p.insertBefore(${frag}, ${endAnchor});`));
|
|
2412
2583
|
ctx.push(`};`);
|
|
2413
2584
|
}
|
|
@@ -2440,8 +2611,19 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
|
|
|
2440
2611
|
}
|
|
2441
2612
|
if (emptyRenderName) {
|
|
2442
2613
|
const isEmptyVar = ctx.n();
|
|
2614
|
+
// A hydrated keyed map attaches its region anchors through
|
|
2615
|
+
// `reconcileHydrated` (items branch) or `__place` (empty branch) — the
|
|
2616
|
+
// anchors are created detached and the empty-fallback builder reads
|
|
2617
|
+
// `anchor.parentNode`, so the empty branch must place them too or the
|
|
2618
|
+
// whole hydrator crashes ("insertBefore on null") and every later
|
|
2619
|
+
// reactive update on the page is lost.
|
|
2620
|
+
const collectVar = hydKeyed ? ctx.n() : null;
|
|
2443
2621
|
ctx.push(`let ${isEmptyVar} = !${hasItems}();`);
|
|
2444
|
-
|
|
2622
|
+
if (collectVar)
|
|
2623
|
+
ctx.push(`const ${collectVar} = [];`);
|
|
2624
|
+
ctx.push(`if (!${isEmptyVar}) { ${initList}(); } else { ${aw}${emptyRenderName}(${collectVar || ''}); }`);
|
|
2625
|
+
if (collectVar)
|
|
2626
|
+
ctx.push(`__place(${anchor}, ${endAnchor}, ${collectVar}, ${parent});`);
|
|
2445
2627
|
if (!reactiveSource)
|
|
2446
2628
|
return null;
|
|
2447
2629
|
pushEffect(`let __first = true;`, `effect(${effOpen}
|
|
@@ -2456,7 +2638,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
|
|
|
2456
2638
|
${effectsVar}.length = 0;
|
|
2457
2639
|
__cleanup(${anchor}, ${endAnchor});
|
|
2458
2640
|
if (__new) { ${initList}(); } else { ${aw}${emptyRenderName}(); }
|
|
2459
|
-
})`);
|
|
2641
|
+
}, true)`);
|
|
2460
2642
|
}
|
|
2461
2643
|
else {
|
|
2462
2644
|
ctx.push(`${initList}();`);
|
|
@@ -2466,7 +2648,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
|
|
|
2466
2648
|
const __nv = ${arrExpr};
|
|
2467
2649
|
if (__first) { __first = false; __nv; ${breakReset}${reconciler}(${arrExpr}); return; }
|
|
2468
2650
|
${breakVar ? `if (${breakVar}) ${breakVar} = false;` : ''}${reconciler}(${arrExpr});
|
|
2469
|
-
})`);
|
|
2651
|
+
}, true)`);
|
|
2470
2652
|
}
|
|
2471
2653
|
}
|
|
2472
2654
|
else {
|
|
@@ -2523,8 +2705,8 @@ ${renderAllItems(' ', null)}
|
|
|
2523
2705
|
__cleanup(${anchor}, ${endAnchor});
|
|
2524
2706
|
if (__new) {
|
|
2525
2707
|
${renderAllItems(' ', null)}
|
|
2526
|
-
} else { ${aw}${emptyRenderName}(
|
|
2527
|
-
})`);
|
|
2708
|
+
} else { ${aw}${emptyRenderName}(); }
|
|
2709
|
+
}, true)`);
|
|
2528
2710
|
}
|
|
2529
2711
|
else {
|
|
2530
2712
|
const collectVar = hyd ? ctx.n() : null;
|
|
@@ -2542,7 +2724,7 @@ ${renderAllItems(' ', null)}
|
|
|
2542
2724
|
${effectsVar}.length = 0;
|
|
2543
2725
|
__cleanup(${anchor}, ${endAnchor});
|
|
2544
2726
|
${renderAllItems(' ', null)}
|
|
2545
|
-
})`);
|
|
2727
|
+
}, true)`);
|
|
2546
2728
|
}
|
|
2547
2729
|
}
|
|
2548
2730
|
return null;
|
|
@@ -2664,6 +2846,11 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
|
|
|
2664
2846
|
}
|
|
2665
2847
|
}
|
|
2666
2848
|
}
|
|
2849
|
+
// The first real claim is this component's root: let it validate the static
|
|
2850
|
+
// class shape against the SSR slot. The flag is consumed by the first claim
|
|
2851
|
+
// and never leaks to nested children.
|
|
2852
|
+
if (ctx.hydrate && ctx.markerless)
|
|
2853
|
+
ctx.descriptorClaim = true;
|
|
2667
2854
|
for (const node of comp.body) {
|
|
2668
2855
|
// A TrackDecl that could not be hoisted (its init reads an earlier body
|
|
2669
2856
|
// binding) is emitted here, in source order, so the binding it reads is
|
|
@@ -2804,6 +2991,33 @@ function buildComponentMap(irRoot, hydrate = false, alloc, markerless = true) {
|
|
|
2804
2991
|
const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames, alloc, markerless);
|
|
2805
2992
|
mapLines.push(`${registry}[${JSON.stringify(comp.name)}] = ${code};`);
|
|
2806
2993
|
}
|
|
2994
|
+
if (hydrate && !markerless) {
|
|
2995
|
+
mapLines.push(`function __reconcileRegion(parent, id, branch, walker) {`);
|
|
2996
|
+
mapLines.push(`\tif (!parent) return;`);
|
|
2997
|
+
mapLines.push(`\tlet start = null;`);
|
|
2998
|
+
mapLines.push(`\tlet end = null;`);
|
|
2999
|
+
mapLines.push(`\tfor (let n = parent.firstChild; n; n = n.nextSibling) {`);
|
|
3000
|
+
mapLines.push(`\t\tif (n.nodeType !== 8) continue;`);
|
|
3001
|
+
mapLines.push(`\t\tconst text = n.textContent || '';`);
|
|
3002
|
+
mapLines.push(`\t\tif (text === 'vesk-r:' + id + ':0' || text === 'vesk-r:' + id + ':1') start = n;`);
|
|
3003
|
+
mapLines.push(`\t\telse if (text === 'vesk-r-end:' + id) end = n;`);
|
|
3004
|
+
mapLines.push(`\t\tif (start && end) break;`);
|
|
3005
|
+
mapLines.push(`\t}`);
|
|
3006
|
+
mapLines.push(`\tif (!start || !end) return;`);
|
|
3007
|
+
mapLines.push(`\tconst ssrBranch = (start.textContent || '').slice(-2) === ':1' ? 1 : 0;`);
|
|
3008
|
+
mapLines.push(`\tif (ssrBranch !== branch) {`);
|
|
3009
|
+
mapLines.push(`\t\tlet n = start.nextSibling;`);
|
|
3010
|
+
mapLines.push(`\t\twhile (n && n !== end) {`);
|
|
3011
|
+
mapLines.push(`\t\t\tconst next = n.nextSibling;`);
|
|
3012
|
+
mapLines.push(`\t\t\tn.remove();`);
|
|
3013
|
+
mapLines.push(`\t\t\tn = next;`);
|
|
3014
|
+
mapLines.push(`\t\t}`);
|
|
3015
|
+
mapLines.push(`\t\tif (walker && walker.retireDetached) walker.retireDetached();`);
|
|
3016
|
+
mapLines.push(`\t}`);
|
|
3017
|
+
mapLines.push(`\tstart.remove();`);
|
|
3018
|
+
mapLines.push(`\tend.remove();`);
|
|
3019
|
+
mapLines.push(`}`);
|
|
3020
|
+
}
|
|
2807
3021
|
mapLines.push(`function __cleanup(start, end) {`);
|
|
2808
3022
|
mapLines.push(`\tlet n = start.nextSibling;`);
|
|
2809
3023
|
mapLines.push(`\twhile (n && n !== end) {`);
|
|
@@ -2870,6 +3084,50 @@ function ssrResidueEstimate(node) {
|
|
|
2870
3084
|
// ServerBlock, ClientBlock, HeadBlock emit no SSR element residue.
|
|
2871
3085
|
return 0;
|
|
2872
3086
|
}
|
|
3087
|
+
function firstBranchRoot(nodes) {
|
|
3088
|
+
for (const n of nodes) {
|
|
3089
|
+
if (n instanceof StaticNode)
|
|
3090
|
+
return { tag: n.tag, descriptor: staticClassDescriptor(n) };
|
|
3091
|
+
if (n instanceof ComponentCall || n instanceof ComponentRef)
|
|
3092
|
+
return null;
|
|
3093
|
+
if (n instanceof OpaqueDynamicRegion)
|
|
3094
|
+
return null;
|
|
3095
|
+
if (n instanceof MapRegion || n instanceof ForLoop || n instanceof WhileLoop) {
|
|
3096
|
+
const child = firstBranchRoot(n.bodyTemplate);
|
|
3097
|
+
if (child)
|
|
3098
|
+
return child;
|
|
3099
|
+
}
|
|
3100
|
+
if (n instanceof SwitchBlock) {
|
|
3101
|
+
for (const c of n.cases) {
|
|
3102
|
+
const child = firstBranchRoot(c.body);
|
|
3103
|
+
if (child)
|
|
3104
|
+
return child;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
if (n instanceof TryCatch) {
|
|
3108
|
+
const child = firstBranchRoot(n.bodyTemplate) || firstBranchRoot(n.catchBody);
|
|
3109
|
+
if (child)
|
|
3110
|
+
return child;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
function structuralRegionMetadata(branches) {
|
|
3116
|
+
const parts = branches.map((nodes) => {
|
|
3117
|
+
const first = firstBranchRoot(nodes);
|
|
3118
|
+
const tag = first ? JSON.stringify(first.tag) : 'undefined';
|
|
3119
|
+
const descriptor = first && first.descriptor ? first.descriptor : 'null';
|
|
3120
|
+
return `{ tag: ${tag}, descriptor: ${descriptor}, count: ${ssrResidueEstimate(nodes)} }`;
|
|
3121
|
+
});
|
|
3122
|
+
return `[${parts.join(', ')}]`;
|
|
3123
|
+
}
|
|
3124
|
+
function emitStructuralRegionDiscard(ctx, branches, selectedExpr, budget) {
|
|
3125
|
+
if (!ctx.hydrate || !ctx.markerless)
|
|
3126
|
+
return;
|
|
3127
|
+
const offset = budget ? `, ${budget}` : '';
|
|
3128
|
+
const consume = budget ? ` ${budget} = 0;` : '';
|
|
3129
|
+
ctx.push(`if (${ctx.walker} && typeof ${ctx.walker}.discardStructuralRegion === 'function' && ${ctx.walker}.discardStructuralRegion(${structuralRegionMetadata(branches)}, ${selectedExpr}${offset})) {${consume}}`);
|
|
3130
|
+
}
|
|
2873
3131
|
function isStaticIR(body) {
|
|
2874
3132
|
for (const node of body) {
|
|
2875
3133
|
if (node instanceof StaticNode) {
|