@vesk/compiler 0.2.57 → 0.2.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EASjB,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,CAU5E;AAED;;;;;;;;;;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;AAoBD,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;AAu8DD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAmJD,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,EASjB,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,CAU5E;AAED;;;;;;;;;;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;AAoBD,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;AAmiED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAmJD,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"}
@@ -989,12 +989,34 @@ function emitClientHead(ctx, node, tracked) {
989
989
  }
990
990
  }
991
991
  }
992
+ /**
993
+ * Whether an expression can change after first render.
994
+ *
995
+ * Two ways it can:
996
+ * 1. it reads `props` or a locally declared `track()` binding — visible
997
+ * statically; or
998
+ * 2. it CALLS something. A call is the only way to hide a cell read behind a
999
+ * function boundary, and hiding one is not exotic — it is the documented
1000
+ * store pattern: a module-scope `track()` cell published as an accessor
1001
+ * (`notificationDrawerCell.get()`, `isAuthenticated()`,
1002
+ * `unreadNotificationCount()`). The compiler cannot see inside those calls,
1003
+ * so it must assume they subscribe; the runtime's `get()` records the
1004
+ * dependency when the expression is evaluated inside the emitted effect,
1005
+ * which is exactly where these conditions are re-evaluated.
1006
+ *
1007
+ * Treating a call as reactive costs one never-firing effect for a genuinely
1008
+ * static condition. The opposite mistake — treating a store-backed condition as
1009
+ * static — is a component that renders once and never updates again.
1010
+ */
992
1011
  function isReactiveExpression(node, tracked) {
993
1012
  const ast = node && (node.ast || node);
994
1013
  if (!ast)
995
1014
  return false;
996
1015
  let reactive = false;
997
1016
  walk(ast, tracked, {
1017
+ CallExpression() {
1018
+ reactive = true;
1019
+ },
998
1020
  Identifier(n, context) {
999
1021
  if (reactive)
1000
1022
  return;
@@ -1236,10 +1258,20 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1236
1258
  ctx.push(`return $f; })();`);
1237
1259
  propsEntries.push(`children: ${frag}`);
1238
1260
  }
1239
- if (ctx.markerless && promptExpr && !plainTarget) {
1240
- // Deposit the residue for the child's first top-level claim (it runs on
1241
- // this same walker with its own zero `topPendingSkip`). A region budget is
1242
- // spent by THIS call, so sibling claims in the branch stay positional.
1261
+ // Deposit the residue for the child's first top-level claim (it runs on
1262
+ // this same walker with its own zero `topPendingSkip`). A region budget is
1263
+ // spent by THIS call, so sibling claims in the branch stay positional.
1264
+ //
1265
+ // The deposit goes down for PLAIN targets too: "imported value" does not mean
1266
+ // "cannot claim". lucide-vesk icons are imported functions that follow the
1267
+ // framework component protocol `(props, registry, walker)` and claim their own
1268
+ // SSR root through it, so without the deposit they claimed the slot holding the
1269
+ // STATIC residue in front of them (`<button><span>Apply Now</span><svg/></button>`)
1270
+ // — the descriptor missed on `<span>` and the walker detached real
1271
+ // server-rendered content. A plain target that does NOT claim simply leaves the
1272
+ // deposit for the post-call adoption claim below, which carries an explicit
1273
+ // offset and therefore consumes/clears it.
1274
+ if (ctx.markerless && promptExpr) {
1243
1275
  ctx.push(`${walkerArg}.injectSkipK(${promptExpr});`);
1244
1276
  }
1245
1277
  ctx.push(`let ${v} = undefined;`);
@@ -2334,6 +2366,42 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
2334
2366
  // budget intact for the next claim.
2335
2367
  topBudget = regionBudgetVar(ctx, 0, undefined, `(typeof __hydrate.takeSkipK === 'function' ? __hydrate.takeSkipK() : 0)`);
2336
2368
  }
2369
+ if (ctx.hydrate) {
2370
+ // A component whose body emits NO claim still rendered SSR content: hydrate
2371
+ // mode never re-creates a static subtree, so that content stays in the DOM
2372
+ // with nobody to own it. The NEXT sibling's claim then landed on it, read
2373
+ // the tag mismatch as a divergence, consumed the slot and DETACHED it —
2374
+ // handing back a fresh element that never reached the document, so this
2375
+ // component rendered but was inert. A bare `effect()` plus static markup is
2376
+ // exactly this case: not "fully static" (so it gets no claimOnly stub from
2377
+ // the component map below) and yet it claims nothing.
2378
+ //
2379
+ // The component claims its OWN slots instead, which is what the fully-static
2380
+ // stub already does. Counting is exact here because the guard below admits
2381
+ // only top-level static/text/runtime nodes: each StaticNode is one server
2382
+ // element, and fragments are flattened into the node list by the IR
2383
+ // generator rather than wrapped. If anything in the body could claim, or
2384
+ // anything is a region/loop/call whose slot count depends on which branch
2385
+ // ran, no prelude is emitted — the callee stays exactly as it was.
2386
+ //
2387
+ // This is compile-time only: no runtime walker change and no per-call-site
2388
+ // marker, so it costs nothing in the client bundle.
2389
+ const bodyNodes = comp.body.filter((node) => !(node instanceof TrackDecl));
2390
+ // Text is never a walker slot: a top-level text binding and a bare text
2391
+ // statement both render into an element's interior, so they neither claim
2392
+ // nor occupy a top-level slot.
2393
+ const slotless = (node) => node instanceof StaticNode || node instanceof TextNode || node instanceof RuntimeStatement
2394
+ || (node instanceof DynamicBinding && node.kind === 'text');
2395
+ const claimless = bodyNodes.every((node) => slotless(node) && !claimsWalkerNode(ctx, node));
2396
+ if (claimless) {
2397
+ const slots = bodyNodes.filter((node) => node instanceof StaticNode).length;
2398
+ for (let i = 0; i < slots; i++) {
2399
+ ctx.push(indent(`__hydrate.claimOnly(undefined, ${i === 0 && topBudget ? topBudget : '0'});`));
2400
+ if (i === 0 && topBudget)
2401
+ ctx.push(indent(`${topBudget} = 0;`));
2402
+ }
2403
+ }
2404
+ }
2337
2405
  for (const node of comp.body) {
2338
2406
  if (node instanceof TrackDecl)
2339
2407
  continue;
@@ -2374,7 +2442,31 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
2374
2442
  ctx.push(indent(`return __pendingChild || $mount;`));
2375
2443
  }
2376
2444
  else {
2377
- ctx.push(indent(`return __pendingChild || $root;`));
2445
+ // A bare `{props.children}` slot is this layout's output, but the layout may
2446
+ // ALSO have siblings of its own (a static island, a `<nav>`). Those land in
2447
+ // `$root`, and returning only the slot threw them away on every FRESH
2448
+ // render — after any SPA navigation the layout was just the page with no
2449
+ // nav, while a hard load of the same URL was fine (SSR emits the whole
2450
+ // chain). Hydrate mode is deliberately untouched: there the return value is
2451
+ // ignored by the router and the claims are already in place in the DOM.
2452
+ //
2453
+ // GATED on the body actually holding a bare slot. In a fresh render
2454
+ // `__pendingChild` is assigned ONLY by the bare-slot branch (every other
2455
+ // slot appends into its parent element), so without a top-level SlotNode
2456
+ // this code is unreachable — and emitting five lines per component cost
2457
+ // ~18 KB across test-app, which is the whole app's route payload. The
2458
+ // promise branch is not dead weight either: the router hands a suspending
2459
+ // page to the layout as a promise, so the slot content really can be one.
2460
+ const hasBareSlot = comp.body.some((node) => node instanceof SlotNode);
2461
+ if (hasBareSlot) {
2462
+ ctx.push(indent(`if (__pendingChild !== null && __pendingChild !== undefined) {`));
2463
+ ctx.push(indent(` if (typeof __pendingChild.then === 'function') return __pendingChild.then((__c) => { $root.appendChild(toDomNode(__c)); return $root; });`));
2464
+ ctx.push(indent(` const __c = toDomNode(__pendingChild);`));
2465
+ ctx.push(indent(` if ($root.childNodes.length === 0) return __c;`));
2466
+ ctx.push(indent(` $root.appendChild(__c);`));
2467
+ ctx.push(indent(`}`));
2468
+ }
2469
+ ctx.push(indent(hasBareSlot ? `return $root;` : `return __pendingChild || $root;`));
2378
2470
  }
2379
2471
  ctx.push(indent(`} finally {`));
2380
2472
  ctx.push(indent(`\tsetActiveComponent(__prev);`));
@@ -1 +1 @@
1
- {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA2T/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;AA4lDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAqV9E"}
1
+ {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA2T/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;AAgmDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAqV9E"}
@@ -2033,6 +2033,11 @@ function warnStaleConstBindings(comp, file) {
2033
2033
  continue;
2034
2034
  if (reactive.has(d.id.name))
2035
2035
  continue;
2036
+ // A FUNCTION initializer is not a snapshot: the body runs whenever the
2037
+ // binding is evaluated, so the tracked read happens there. Reporting it
2038
+ // pushed authors to inline helpers that were already correct.
2039
+ if (d.init.type === 'ArrowFunctionExpression' || d.init.type === 'FunctionExpression')
2040
+ continue;
2036
2041
  // A bare identifier initializer is an ALIAS, not a read: `const copy =
2037
2042
  // selCell` re-binds the cell and stays live. Only a read snapshots.
2038
2043
  if (d.init.type === 'Identifier' && reactive.has(d.init.name))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.57",
3
+ "version": "0.2.59",
4
4
  "type": "module",
5
5
  "description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@sveltejs/acorn-typescript": "^1.0.0",
23
- "@vesk/types": "^0.2.57",
23
+ "@vesk/types": "^0.2.59",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",