@vesk/compiler 0.2.42 → 0.2.44

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 (42) hide show
  1. package/dist/actions.d.ts.map +1 -1
  2. package/dist/actions.js +17 -1
  3. package/dist/client-codegen.d.ts +25 -0
  4. package/dist/client-codegen.d.ts.map +1 -1
  5. package/dist/client-codegen.js +225 -15
  6. package/dist/errors.d.ts +14 -0
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +36 -0
  9. package/dist/ir-generator.d.ts.map +1 -1
  10. package/dist/ir-generator.js +466 -199
  11. package/dist/ir.d.ts +22 -1
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +18 -1
  14. package/dist/parser.d.ts.map +1 -1
  15. package/dist/parser.js +25 -1
  16. package/dist/precompile-runtime.d.ts.map +1 -1
  17. package/dist/precompile-runtime.js +4 -0
  18. package/dist/precompile.js +6 -2
  19. package/dist/server-jsgen.d.ts.map +1 -1
  20. package/dist/server-jsgen.js +8 -1
  21. package/dist/server-render.d.ts.map +1 -1
  22. package/dist/server-render.js +138 -56
  23. package/dist/server-utils.d.ts.map +1 -1
  24. package/dist/server-utils.js +33 -5
  25. package/dist/ssr-store.d.ts +60 -0
  26. package/dist/ssr-store.d.ts.map +1 -1
  27. package/dist/ssr-store.js +157 -6
  28. package/dist/tokens.d.ts +28 -0
  29. package/dist/tokens.d.ts.map +1 -1
  30. package/dist/tokens.js +200 -19
  31. package/dist/types.d.ts +1 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/vesk-plugin.d.ts +2 -0
  34. package/dist/vesk-plugin.d.ts.map +1 -1
  35. package/dist/vesk-plugin.js +28 -1
  36. package/dist/vsk-collision.d.ts +44 -0
  37. package/dist/vsk-collision.d.ts.map +1 -0
  38. package/dist/vsk-collision.js +73 -0
  39. package/dist/vsk-imports.d.ts +51 -0
  40. package/dist/vsk-imports.d.ts.map +1 -1
  41. package/dist/vsk-imports.js +106 -9
  42. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAW9C;AA6BD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAqCtF;AAeD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAOvG;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBzD"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAW9C;AA6BD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAqCtF;AAeD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAWvG;AAaD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBzD"}
package/dist/actions.js CHANGED
@@ -3,6 +3,7 @@ import { print } from 'esrap';
3
3
  import ts from 'esrap/languages/ts';
4
4
  import { parse } from '@vesk/compiler/src/parser';
5
5
  import { stripTsTypes, hasTsSyntax, isTypeOnlyStatement, stripCodeTypes } from '@vesk/compiler/src/strip-ts';
6
+ import { stripTrackDeclMarkers } from '@vesk/compiler/src/tokens';
6
7
  export const ACTION_PREFIX = '/_vesk/action/';
7
8
  export function hashString(str) {
8
9
  let h1 = 0x811c9dc5;
@@ -102,12 +103,27 @@ function printWithTypesStripped(ast, code, hadTs = hasTsSyntax(ast)) {
102
103
  export function transformTopLevelForActions(topLevelCode, mode) {
103
104
  const out = [];
104
105
  for (const c of topLevelCode) {
105
- const rewritten = rewriteTopLevelActions(c, mode);
106
+ // A top-level TrackDecl (`const &[count] = track(0)`) is parser-only syntax:
107
+ // the `&` is still in this text, and both consumers are plain JavaScript
108
+ // engines — `new Function` on the server, a browser module parse for the
109
+ // client. Blank the markers before either sees the code.
110
+ const rewritten = rewriteTopLevelActions(stripTopLevelTrackDecls(c), mode);
106
111
  if (rewritten !== '')
107
112
  out.push(rewritten);
108
113
  }
109
114
  return out;
110
115
  }
116
+ /** Remove TrackDecl `&` markers from a top-level code chunk. */
117
+ function stripTopLevelTrackDecls(code) {
118
+ let ast;
119
+ try {
120
+ ast = parse(code);
121
+ }
122
+ catch {
123
+ return code;
124
+ }
125
+ return stripTrackDeclMarkers(code, 0, ast);
126
+ }
111
127
  /** Collect the stable action ids defined by a page source (for the manifest). */
112
128
  export function collectActionIds(source) {
113
129
  const ids = [];
@@ -8,6 +8,18 @@ export interface TrackedInfo {
8
8
  export declare function semicolonizeStatement(code: string): string;
9
9
  export declare function transformTracked(irNode: Expression | RuntimeStatement | DynamicBinding, tracked: Map<string, TrackedInfo>): string;
10
10
  export declare function collectTrackedNames(body: IRNode[]): Map<string, TrackedInfo>;
11
+ /**
12
+ * Collect the tracked bindings a prop expression READS, so a string-returning
13
+ * component call can be re-rendered when one of them changes.
14
+ *
15
+ * `primitive()` / `leaf()` bodies run once and their markup is converted once,
16
+ * so a tracked read inside them subscribes nothing. The call site passes the
17
+ * deps to {@link rerenderNode}, which re-reads them inside an effect.
18
+ *
19
+ * Property keys and member properties (`obj.bump`, `x.set(...)`) are not
20
+ * tracked reads — only the object positions are, matching `transformTracked`.
21
+ */
22
+ export declare function collectTrackedDeps(value: unknown, tracked: Map<string, TrackedInfo>): string[];
11
23
  /**
12
24
  * Tracked reads inside a track/derived declaration init are re-emitted as
13
25
  * `get(…)` subscriptions so a `derived(() => … cell …)` (or
@@ -56,6 +68,19 @@ export declare function compileClientBoth(source: string, _componentName: string
56
68
  comp: string;
57
69
  hyd: string;
58
70
  name: string | null;
71
+ aliases: Array<{
72
+ local: string;
73
+ exported: string;
74
+ }>;
75
+ reexportPaths: string[];
76
+ componentNames: string[];
59
77
  };
78
+ /**
79
+ * The component names a `.vsk` file declares. Used by the bundle to spot
80
+ * same-name declarations across files, which collide on the single global
81
+ * registry. Prefer the free `componentNames` from `compileClientBoth`; this
82
+ * exists for call sites that already have the source but no IR to hand.
83
+ */
84
+ export declare function vskComponentNames(source: string, sourcePath?: string): string[];
60
85
  export { compileClient as compile, isStaticIR };
61
86
  //# sourceMappingURL=client-codegen.d.ts.map
@@ -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;AAyCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAiHlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;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;AA83DD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAmJD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAqED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,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,CAAA;CAAE,CA0BpD;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;AAi7DD,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"}
@@ -10,6 +10,7 @@ import { extractRuntimeNames, extractTopLevelNames } from '@vesk/compiler/src/se
10
10
  import { importBindingPairs, localValueImportNames } from '@vesk/compiler/src/module-imports';
11
11
  import { stripTrackGeneric } from '@vesk/compiler/src/scan';
12
12
  import { importModuleTarget } from '@vesk/compiler/src/tokens';
13
+ import { collectVskReexportPaths, vskRegistryAliases } from '@vesk/compiler/src/vsk-imports';
13
14
  import { inlineMdImportsFrom } from '@vesk/compiler/src/md-inline';
14
15
  import { stripTsTypes, hasTsSyntax } from '@vesk/compiler/src/strip-ts';
15
16
  function memberExpr(object, property) {
@@ -42,6 +43,66 @@ function simpleGetName(expr) {
42
43
  }
43
44
  return inner;
44
45
  }
46
+ const BINDING_REF_CALLS = ['bindValue', 'bindChecked', 'bindGroup'];
47
+ /**
48
+ * A binding ref (`bindValue`/`bindChecked`/`bindGroup`) two-way-binds the raw
49
+ * tracked CELL — the web runtime expects the cell object, never its current
50
+ * value. transformTracked rewrites a virtual tracked name read to
51
+ * `get(cell)`, so `ref={bindValue(name)}` would compile to
52
+ * `bindValue(get(cell))` and hand the binding the cell's value. Untrack the
53
+ * first argument back to the bare cell name (`bindValue(cell)`), mirroring
54
+ * how a plain `ref={cell}` emits `set(cell, el)`. Function-form bindings
55
+ * (`bindValue(() => get(x), v => set(x, v))`) never start with `get(` and
56
+ * pass through unchanged. String scan only — no regex.
57
+ */
58
+ function untrackBindingArg(expr) {
59
+ let head = -1;
60
+ for (const name of BINDING_REF_CALLS) {
61
+ if (expr.startsWith(`${name}(`)) {
62
+ head = name.length;
63
+ break;
64
+ }
65
+ }
66
+ if (head < 0)
67
+ return expr;
68
+ const rest = expr.slice(head + 1);
69
+ if (rest[0] !== 'g' || rest[1] !== 'e' || rest[2] !== 't' || rest[3] !== '(')
70
+ return expr;
71
+ let i = 4;
72
+ let word = '';
73
+ for (; i < rest.length; i++) {
74
+ const c = rest.charCodeAt(i);
75
+ const ok = (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95 || c === 36;
76
+ if (!ok)
77
+ break;
78
+ word += rest[i];
79
+ }
80
+ if (word.length === 0 || rest[i] !== ')')
81
+ return expr;
82
+ const after = i + 1;
83
+ if (rest[after] !== ',' && rest[after] !== ')')
84
+ return expr;
85
+ return expr.slice(0, head + 1) + word + rest.slice(after);
86
+ }
87
+ /**
88
+ * For a `ref=` binding whose expression resolves to a tracked cell — either a
89
+ * virtual name (`ref={box}` → emitted `get(boxCell)`) or the raw cell
90
+ * (`ref={boxCell}` → emitted `boxCell`) — the compiled mount must SET the cell
91
+ * to the element, not call the cell's current value. The old `(get(x))(el)`
92
+ * call form is only correct for function refs (`ref={el => x = el}`).
93
+ * Returns the cell name to assign, or null to keep the call form.
94
+ */
95
+ function trackedRefTarget(expr, tracked) {
96
+ const cell = simpleGetName(expr);
97
+ if (cell !== null)
98
+ return cell;
99
+ if (/^[A-Za-z_$][\w$]*$/.test(expr)) {
100
+ const info = tracked.get(expr);
101
+ if (info && info.kind === 'cell' && info.cellName === expr)
102
+ return expr;
103
+ }
104
+ return null;
105
+ }
45
106
  export function semicolonizeStatement(code) {
46
107
  const trimmed = code.trimEnd();
47
108
  if (!trimmed || trimmed.endsWith(';') || trimmed.endsWith('}'))
@@ -162,6 +223,25 @@ export function transformTracked(irNode, tracked) {
162
223
  }
163
224
  return context.next();
164
225
  },
226
+ Property(node, context) {
227
+ // Shorthand `{ bump }` shares ONE node for key and value, so the key
228
+ // skip below cannot apply. Rewriting the value makes the property
229
+ // non-shorthand, and the shorthand flag has to go with it — otherwise
230
+ // the printer emits `get(cell): get(cell)`, which is not parseable.
231
+ if (node.shorthand && node.value && node.value.type === 'Identifier' && !node.computed) {
232
+ const info = context.state.get(node.value.name);
233
+ if (info && info.kind === 'virtual') {
234
+ return {
235
+ ...node,
236
+ shorthand: false,
237
+ value: callExpr({ type: 'Identifier', name: 'get' }, [
238
+ { type: 'Identifier', name: info.cellName },
239
+ ]),
240
+ };
241
+ }
242
+ }
243
+ return context.next();
244
+ },
165
245
  Identifier(node, context) {
166
246
  const parent = context.path.at(-1);
167
247
  if (parent) {
@@ -184,6 +264,16 @@ export function transformTracked(irNode, tracked) {
184
264
  parent.key.type === 'Identifier' &&
185
265
  parent.key.name === 'into')
186
266
  return context.next();
267
+ // A non-computed, non-shorthand property KEY is a name, not a read:
268
+ // `{ bump: bump }` must keep the key `bump` even when `bump` is a
269
+ // tracked cell — rewriting it yields the unparseable
270
+ // `{ get(bump): get(bump) }`. Shorthand keys are the value node, so
271
+ // they fall through to the rewrite below (with `shorthand` cleared).
272
+ if (parent.type === 'Property' &&
273
+ parent.key === node &&
274
+ !parent.computed &&
275
+ !parent.shorthand)
276
+ return context.next();
187
277
  if (parent.type === 'MemberExpression' &&
188
278
  !parent.computed &&
189
279
  parent.object === node &&
@@ -216,6 +306,42 @@ export function collectTrackedNames(body) {
216
306
  }
217
307
  return names;
218
308
  }
309
+ /**
310
+ * Collect the tracked bindings a prop expression READS, so a string-returning
311
+ * component call can be re-rendered when one of them changes.
312
+ *
313
+ * `primitive()` / `leaf()` bodies run once and their markup is converted once,
314
+ * so a tracked read inside them subscribes nothing. The call site passes the
315
+ * deps to {@link rerenderNode}, which re-reads them inside an effect.
316
+ *
317
+ * Property keys and member properties (`obj.bump`, `x.set(...)`) are not
318
+ * tracked reads — only the object positions are, matching `transformTracked`.
319
+ */
320
+ export function collectTrackedDeps(value, tracked) {
321
+ const ast = value?.ast;
322
+ if (!ast || typeof ast !== 'object' || tracked.size === 0)
323
+ return [];
324
+ const found = [];
325
+ const seen = new Set();
326
+ walk(ast, tracked, {
327
+ Identifier(node, context) {
328
+ const parent = context.path.at(-1);
329
+ if (parent) {
330
+ if (parent.type === 'MemberExpression' && !parent.computed && parent.property === node)
331
+ return context.next();
332
+ if (parent.type === 'Property' && parent.key === node)
333
+ return context.next();
334
+ }
335
+ const info = context.state.get(node.name);
336
+ if (info && !seen.has(node.name)) {
337
+ seen.add(node.name);
338
+ found.push(node.name);
339
+ }
340
+ return context.next();
341
+ },
342
+ });
343
+ return found;
344
+ }
219
345
  /**
220
346
  * Tracked reads inside a track/derived declaration init are re-emitted as
221
347
  * `get(…)` subscriptions so a `derived(() => … cell …)` (or
@@ -597,8 +723,9 @@ function emitStatic(ctx, node, tracked, effectsVar, skipK = 0, regionBudget) {
597
723
  const target = attr.target || '';
598
724
  const isEvent = target.startsWith('on') && target.length > 2;
599
725
  if (target === 'ref') {
600
- const expr = transformTracked(attr.expression, tracked);
601
- ctx.push(`(${expr})(${ssrEl});`);
726
+ const expr = untrackBindingArg(transformTracked(attr.expression, tracked));
727
+ const cell = trackedRefTarget(expr, tracked);
728
+ ctx.push(cell !== null ? `set(${cell}, ${ssrEl});` : `(${expr})(${ssrEl});`);
602
729
  }
603
730
  else if (isEvent) {
604
731
  const eventName = target.slice(2).toLowerCase();
@@ -689,8 +816,9 @@ function emitStatic(ctx, node, tracked, effectsVar, skipK = 0, regionBudget) {
689
816
  const target = attr.target || '';
690
817
  const isEvent = target.startsWith('on') && target.length > 2;
691
818
  if (target === 'ref') {
692
- const expr = transformTracked(attr.expression, tracked);
693
- ctx.push(`(${expr})(${el});`);
819
+ const expr = untrackBindingArg(transformTracked(attr.expression, tracked));
820
+ const cell = trackedRefTarget(expr, tracked);
821
+ ctx.push(cell !== null ? `set(${cell}, ${el});` : `(${expr})(${el});`);
694
822
  }
695
823
  else if (isEvent) {
696
824
  const eventName = target.slice(2).toLowerCase();
@@ -962,7 +1090,36 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
962
1090
  // as `children`: a fresh DocumentFragment per slot hoisted from the IR.
963
1091
  const slotChildren = node.children.filter((c) => c instanceof PropSlot);
964
1092
  const regChildren = node.children.filter((c) => !(c instanceof PropSlot));
965
- const callArgs = () => `{ ${propsEntries.join(', ')} }`;
1093
+ // Call targets that are not known compiled components (imported values,
1094
+ // member-expression tags) cannot claim, so the caller adopts their SSR root
1095
+ // itself. They also do not run the `props = reactiveProps(props)` prologue
1096
+ // that compiled components carry, so tracked cells passed as props would
1097
+ // reach them raw — the server unwraps them, so a `primitive()`/`leaf()`
1098
+ // reading `props.bump` would render `0[object Object]` on the client only.
1099
+ // Wrap the literal here to keep both sides reading values.
1100
+ const plainTarget = !!(calleeExpr || ctx.importedNames.has(node.componentName)) && !ctx.selfClaimNames.has(node.componentName);
1101
+ // Tracked bindings this call's markup is derived from. Only a plain target
1102
+ // needs them: a compiled component re-runs its own bindings, but a
1103
+ // `primitive()`/`leaf()`/imported helper that READS a cell through
1104
+ // `reactiveProps` would otherwise leave its markup stale forever.
1105
+ const trackedDeps = [];
1106
+ if (plainTarget) {
1107
+ for (const p of node.props) {
1108
+ if (typeof p.value === 'string')
1109
+ continue;
1110
+ for (const d of collectTrackedDeps(p.value, tracked))
1111
+ if (!trackedDeps.includes(d))
1112
+ trackedDeps.push(d);
1113
+ }
1114
+ for (const sp of node.spreadProps) {
1115
+ for (const d of collectTrackedDeps(sp, tracked))
1116
+ if (!trackedDeps.includes(d))
1117
+ trackedDeps.push(d);
1118
+ }
1119
+ }
1120
+ const callArgs = () => (plainTarget
1121
+ ? `reactiveProps({ ${propsEntries.join(', ')} })`
1122
+ : `{ ${propsEntries.join(', ')} }`);
966
1123
  if (ctx.hydrate) {
967
1124
  const access = calleeExpr
968
1125
  ?? (ctx.importedNames.has(node.componentName)
@@ -982,7 +1139,6 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
982
1139
  // compiled components (imported values, member-expression tags) cannot
983
1140
  // claim, so after the call we adopt their SSR root ourselves and replace it
984
1141
  // with the fresh node they returned — never a duplicate, never a box.
985
- const plainTarget = !!(calleeExpr || ctx.importedNames.has(node.componentName)) && !ctx.selfClaimNames.has(node.componentName);
986
1142
  const walkerArg = ctx.walker;
987
1143
  // Markerless value-thread offset (Phase 3.c): the residue that precedes THIS
988
1144
  // call site — a static sibling the walker never claimed (`skipK`), or a
@@ -1001,13 +1157,21 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1001
1157
  const maybeReplace = (v) => {
1002
1158
  if (!plainTarget)
1003
1159
  return;
1004
- // nodeType 11 = DocumentFragment: self-claiming imports (Link/Md/Form)
1160
+ // nodeType 11 = DocumentFragment. Self-claiming imports (Link/Md/Form)
1005
1161
  // return a fragment when they adopted their SSR root, so there is nothing
1006
- // to replace — claiming here would steal the NEXT sibling's root.
1162
+ // to replace — claiming here would steal the NEXT sibling's root. Those
1163
+ // are excluded by `plainTarget` above, so a fragment reaching this branch
1164
+ // is a plain target that returned SEVERAL roots (a leaf/primitive string
1165
+ // with siblings in it) and its children must be spliced in. Branch on the
1166
+ // runtime nodeType rather than guessing at the target's authoring style.
1007
1167
  const claim = promptExpr
1008
1168
  ? `${walkerArg}.claimOnly(undefined, ${promptExpr})`
1009
1169
  : `${walkerArg}.claimOnly()`;
1010
- ctx.push(`if (${v} && ${v}.parentNode == null && ${v}.nodeType !== 11) { const __sr = ${claim}; if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v}, __sr); }`);
1170
+ // A several-root render owns as many SSR slots as it has roots. Claiming
1171
+ // one and splicing the rest in front of it left the unclaimed SSR
1172
+ // siblings in place, duplicating them; the extra claims are removed with
1173
+ // the first so the fresh roots land exactly where the SSR range sat.
1174
+ ctx.push(`if (${v} && ${v}.parentNode == null) { if (${v}.nodeType === 11) { const __cnt = ${v}.childNodes.length; let __sr = null; for (let __i = 0; __i < __cnt; __i++) { const __c = ${claim}; if (__c && __c.parentNode) { if (__sr) { __c.remove(); } else { __sr = __c; } } } if (__sr) { const __p = __sr.parentNode; while (${v}.firstChild) __p.insertBefore(${v}.firstChild, __sr); __p.removeChild(__sr); } } else { const __sr = ${claim}; if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v}, __sr); } }`);
1011
1175
  // A plain target consumes the region budget at adoption; subsequent
1012
1176
  // claims in the enclosing branch must resolve positionally from here.
1013
1177
  if (regionBudget && ctx.markerless)
@@ -1058,7 +1222,13 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1058
1222
  }
1059
1223
  ctx.push(`let ${v} = undefined;`);
1060
1224
  ctx.push(`if (__hydrate) __hydrate.childFrame = (__hydrate.childFrame || 0) + 1;`);
1061
- ctx.push(`try { ${v} = ${awaitKw}${access}(${callArgs()}, __registry, ${walkerArg}); } finally { if (__hydrate) __hydrate.childFrame--; }`);
1225
+ ctx.push(`try { ${v} = toDomNode(${awaitKw}${access}(${callArgs()}, __registry, ${walkerArg})); } finally { if (__hydrate) __hydrate.childFrame--; }`);
1226
+ // Snapshot a several-root render's roots HERE, while the fragment still
1227
+ // holds them: adoption drains it, and an emptied fragment is no handle for
1228
+ // `rerenderNode` to follow on the next update.
1229
+ const rootsVar = trackedDeps.length > 0 ? ctx.n() : null;
1230
+ if (rootsVar)
1231
+ ctx.push(`const ${rootsVar} = ${v} && ${v}.nodeType === 11 ? Array.from(${v}.childNodes) : [${v}];`);
1062
1232
  // Spend the region budget only once the call RETURNS: a self-claiming child
1063
1233
  // that throws before claiming (the try/catch fallback in / with fail:true)
1064
1234
  // consumed the takeSkipK deposit but never advanced the walker cursor, so the
@@ -1069,6 +1239,9 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1069
1239
  ctx.push(`${regionBudget} = 0;`);
1070
1240
  maybeReplace(v);
1071
1241
  maybeRetire();
1242
+ if (trackedDeps.length > 0) {
1243
+ ctx.push(`rerenderNode(${rootsVar}, [${trackedDeps.join(', ')}], () => ${awaitKw}${access}(${callArgs()}, __registry, ${walkerArg}));`);
1244
+ }
1072
1245
  return v;
1073
1246
  }
1074
1247
  else {
@@ -1093,14 +1266,24 @@ function emitComponentCall(ctx, node, tracked, effectsVar, parentVar, compPrefix
1093
1266
  propsEntries.push(`children: ${frag}`);
1094
1267
  }
1095
1268
  if (calleeExpr) {
1096
- ctx.push(`const ${v} = ${calleeExpr}(${callArgs()});`);
1269
+ ctx.push(`const ${v} = toDomNode(${calleeExpr}(${callArgs()}));`);
1097
1270
  }
1098
1271
  else if (ctx.importedNames.has(node.componentName)) {
1099
- ctx.push(`const ${v} = ${awaitKw}${node.componentName}(${callArgs()});`);
1272
+ ctx.push(`const ${v} = toDomNode(${awaitKw}${node.componentName}(${callArgs()}));`);
1100
1273
  }
1101
1274
  else {
1102
1275
  ctx.push(`if (${compPrefix}[${JSON.stringify(node.componentName)}] == null) throw new Error(${JSON.stringify(`Component "${node.componentName}" was not found while rendering. Declare it with the \`component\` keyword, import it, or register it in the component registry.`)});`);
1103
- ctx.push(`const ${v} = ${awaitKw}${compPrefix}[${JSON.stringify(node.componentName)}](${callArgs()});`);
1276
+ ctx.push(`const ${v} = toDomNode(${awaitKw}${compPrefix}[${JSON.stringify(node.componentName)}](${callArgs()}));`);
1277
+ }
1278
+ if (trackedDeps.length > 0) {
1279
+ const calleeRef = calleeExpr
1280
+ ?? (ctx.importedNames.has(node.componentName)
1281
+ ? node.componentName
1282
+ : `${compPrefix}[${JSON.stringify(node.componentName)}]`);
1283
+ // Snapshot the roots while the fragment still holds them — see above.
1284
+ const rootsVar2 = ctx.n();
1285
+ ctx.push(`const ${rootsVar2} = ${v} && ${v}.nodeType === 11 ? Array.from(${v}.childNodes) : [${v}];`);
1286
+ ctx.push(`rerenderNode(${rootsVar2}, [${trackedDeps.join(', ')}], () => ${awaitKw}${calleeRef}(${callArgs()}));`);
1104
1287
  }
1105
1288
  }
1106
1289
  return v;
@@ -2554,7 +2737,9 @@ function emitClientFromIR(ir, options) {
2554
2737
  }
2555
2738
  }
2556
2739
  const exportCode = exportLines.join('\n');
2557
- const runtimeNames = ['track', 'get', 'set', 'destroy_block', 'getActiveComponent', 'setActiveComponent', 'reactiveProps', 'applyStyle'];
2740
+ // `toDomNode` normalizes a component call's return (a primitive/leaf string)
2741
+ // into a Node; it is emitted at every call site, so it is always needed.
2742
+ const runtimeNames = ['track', 'get', 'set', 'destroy_block', 'getActiveComponent', 'setActiveComponent', 'reactiveProps', 'applyStyle', 'toDomNode', 'rerenderNode'];
2558
2743
  if (ir.components.some(c => !isStaticIR(c.body)))
2559
2744
  runtimeNames.push('effect');
2560
2745
  for (const name of usedRuntimeBindings(ir))
@@ -2638,12 +2823,37 @@ export function compileClientBoth(source, _componentName, sourcePath, opts) {
2638
2823
  // allocator: hyd continues after the names comp already consumed.
2639
2824
  const alloc = nameAllocFor(sourcePath);
2640
2825
  const comp = emitClientFromIR(ir, { forceClient: true, nameAllocator: alloc });
2826
+ // `.vsk` modules are registry-keyed, not real ES modules: `export { A as B }`
2827
+ // and `import { A as B } from './x.vsk'` are aliases the client bundle must
2828
+ // register on `__components`, since nothing is emitted for them.
2829
+ const aliases = vskRegistryAliases(ir.imports, ir.exportAliases);
2830
+ // `export * from './x.vsk'` targets must be compiled and registered in this
2831
+ // file's chunk too, exactly like `.vsk` imports.
2832
+ const reexportPaths = sourcePath
2833
+ ? collectVskReexportPaths(ir.reexportSources, sourcePath)
2834
+ : [];
2641
2835
  if (opts?.skipHyd)
2642
- return { comp, hyd: '', name };
2836
+ return { comp, hyd: '', name, aliases, reexportPaths, componentNames: ir.components.map((c) => c.name) };
2643
2837
  return {
2644
2838
  comp,
2645
2839
  hyd: emitClientFromIR(irHyd, { forceClient: true, hydrate: true, includeTopLevel: false, nameAllocator: alloc, markerless: opts?.markerless !== false }),
2646
2840
  name,
2841
+ aliases,
2842
+ reexportPaths,
2843
+ // Every component NAME this file registers, not just the resolved one —
2844
+ // the bundle needs the full list to spot same-name declarations across
2845
+ // files (see findVskComponentCollisions).
2846
+ componentNames: ir.components.map((c) => c.name),
2647
2847
  };
2648
2848
  }
2849
+ /**
2850
+ * The component names a `.vsk` file declares. Used by the bundle to spot
2851
+ * same-name declarations across files, which collide on the single global
2852
+ * registry. Prefer the free `componentNames` from `compileClientBoth`; this
2853
+ * exists for call sites that already have the source but no IR to hand.
2854
+ */
2855
+ export function vskComponentNames(source, sourcePath) {
2856
+ const ast = parse(source, sourcePath ? { filename: sourcePath } : {});
2857
+ return generateIR(ast, source, sourcePath).components.map((c) => c.name);
2858
+ }
2649
2859
  export { compileClient as compile, isStaticIR };
package/dist/errors.d.ts CHANGED
@@ -22,6 +22,20 @@ export declare class VeskError extends Error {
22
22
  code?: string;
23
23
  constructor(message: string, opts?: VeskErrorOptions);
24
24
  static notFound(name: string, candidates?: string[], context?: VeskErrorOptions): VeskError;
25
+ /**
26
+ * A `.vsk` namespace is resolved at the JSX-tag level: `<ns.Icon />` looks
27
+ * the component up in the registry by its exported name, exactly like
28
+ * `import { Icon }`, and no module object is ever built. The two forms that
29
+ * genuinely need a real module object are reported here.
30
+ *
31
+ * - `nested` — `<ns.Sub.Icon />`. A `.vsk` module's exports are flat
32
+ * component names, so there is no object to walk into.
33
+ * - `value` — `ns.max` read as a value. Component tags resolve; plain value
34
+ * reads do not, because nothing binds `ns` at runtime.
35
+ */
36
+ static vskNamespaceMember(context?: VeskErrorOptions & {
37
+ form?: 'nested' | 'value';
38
+ }): VeskError;
25
39
  static classDecl(context?: VeskErrorOptions): VeskError;
26
40
  static serverBlockInClient(compName: string, context?: VeskErrorOptions): VeskError;
27
41
  static clientBlockInServer(compName: string, context?: VeskErrorOptions): VeskError;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAI,GAAG,MAAM,CAkBrG;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA8BnG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAoB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAYvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB/G,MAAM,CAAC,cAAc,CAAC,OAAO,GAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,SAAS;IAmBpF,QAAQ,IAAI,MAAM;IA0BlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAI,GAAG,MAAM,CAkBrG;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA8BnG;;;;;;;;;;OAUG;IACH,MAAM,CAAC,kBAAkB,CAAC,OAAO,GAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;KAAO,GAAG,SAAS;IA6BpG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAoB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAYvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB/G,MAAM,CAAC,cAAc,CAAC,OAAO,GAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,SAAS;IAmBpF,QAAQ,IAAI,MAAM;IA0BlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
package/dist/errors.js CHANGED
@@ -150,6 +150,42 @@ export class VeskError extends Error {
150
150
  : '',
151
151
  });
152
152
  }
153
+ /**
154
+ * A `.vsk` namespace is resolved at the JSX-tag level: `<ns.Icon />` looks
155
+ * the component up in the registry by its exported name, exactly like
156
+ * `import { Icon }`, and no module object is ever built. The two forms that
157
+ * genuinely need a real module object are reported here.
158
+ *
159
+ * - `nested` — `<ns.Sub.Icon />`. A `.vsk` module's exports are flat
160
+ * component names, so there is no object to walk into.
161
+ * - `value` — `ns.max` read as a value. Component tags resolve; plain value
162
+ * reads do not, because nothing binds `ns` at runtime.
163
+ */
164
+ static vskNamespaceMember(context = {}) {
165
+ const nested = context.form === 'nested';
166
+ return new VeskError(nested
167
+ ? 'A `.vsk` namespace is flat, so `<ns.Sub.Icon />` cannot be resolved.'
168
+ : 'A `.vsk` namespace resolves component tags only, so `ns.value` is not available.', {
169
+ ...context,
170
+ code: context.code || 'V0410',
171
+ suggestions: nested
172
+ ? [
173
+ "Import the component by name: import { Icon } from './icons.vsk'",
174
+ "Import from the module that owns Sub: import * as Sub from './sub.vsk'",
175
+ ]
176
+ : [
177
+ "Import the value by name: import { MAX } from './constants.vsk'",
178
+ 'Move the value into a .ts module and import it from there — those are real ES modules with live bindings.',
179
+ ],
180
+ nextSteps: [
181
+ 'A `.vsk` tag resolves by component name in the global registry, not by module exports, so `import * as ns` + `<ns.Icon />` needs no module object.',
182
+ nested
183
+ ? 'Re-export with a flat name (`export { Icon }`) or import the component directly.'
184
+ : 'Re-export the value under a flat name and import it directly, or keep the value in a .ts module.',
185
+ ],
186
+ tip: '`<ns.Icon />` works; only nested paths and value reads need a real module namespace.',
187
+ });
188
+ }
153
189
  static classDecl(context = {}) {
154
190
  return new VeskError('class declarations are not supported inside Vesk components.', {
155
191
  ...context,
@@ -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;AA48BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA6N9E"}
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;AA6nCD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAgS9E"}