@vesk/compiler 0.2.47 → 0.2.48

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,5 +1,18 @@
1
1
  import type { ComponentIR } from '@vesk/compiler/src/ir';
2
- export declare function renderHeadHtml(comp: ComponentIR, props?: Record<string, unknown>): string;
2
+ /**
3
+ * Serialize one component's `<Head>` block.
4
+ *
5
+ * `scope` is the component's `__vesk` bindings (imports, runtime names, the
6
+ * sub-components its body destructures). The head is assembled by re-walking
7
+ * the IR and re-evaluating the expressions rather than by capturing the real
8
+ * render, so the re-evaluation needs the same bindings the component had —
9
+ * without them every expression that touched an import threw and was swallowed,
10
+ * and the served document shipped an empty `<title>`/`<meta>` while the page
11
+ * body rendered fine (and the client corrected it on hydration, which is why
12
+ * this only ever showed up in view-source, in crawlers, and after an SPA nav
13
+ * re-applied the server head).
14
+ */
15
+ export declare function renderHeadHtml(comp: ComponentIR, props?: Record<string, unknown>, scope?: Record<string, unknown>): string;
3
16
  export declare function mergeHeadHtml(pageHead: string, layoutHead: string): {
4
17
  html: string;
5
18
  conflicts: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"server-head.d.ts","sourceRoot":"","sources":["../src/server-head.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAyHzD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAe7F;AAoFD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAkExI"}
1
+ {"version":3,"file":"server-head.d.ts","sourceRoot":"","sources":["../src/server-head.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAgVzD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,EACjB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,MAAM,CAkBR;AAoFD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAkExI"}
@@ -1,6 +1,9 @@
1
- import { StaticNode, TextNode, DynamicBinding, HeadBlock, RuntimeStatement } from '@vesk/compiler/src/ir';
1
+ import { StaticNode, TextNode, DynamicBinding, HeadBlock, RuntimeStatement, TrackDecl } from '@vesk/compiler/src/ir';
2
2
  import { tryEvalExpr, escapeHtml } from '@vesk/compiler/src/server-utils';
3
3
  import { isWhitespaceChar } from '@vesk/compiler/src/scan';
4
+ import { parse } from '@vesk/compiler/src/parser';
5
+ import { collectTrackedNames, transformTracked, transformTrackedInit } from '@vesk/compiler/src/client-codegen';
6
+ import { walk } from 'zimmerframe';
4
7
  function isHtmlNameChar(ch) {
5
8
  const code = ch.charCodeAt(0);
6
9
  return ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || ch === '-');
@@ -11,22 +14,206 @@ function isAttrBoundaryChar(ch) {
11
14
  function isValueBoundaryChar(ch) {
12
15
  return isWhitespaceChar(ch) || ch === '/' || ch === '>';
13
16
  }
14
- function evaluateLocals(comp, props) {
17
+ function headCell(value) {
18
+ const cell = {
19
+ __vskHeadValue: value,
20
+ toString() { return String(cell.__vskHeadValue); },
21
+ // Present so `+count` / `String(count)` on the cell itself still yields the
22
+ // value; the return type is widened because `valueOf` must return `this`.
23
+ valueOf() { return cell; },
24
+ };
25
+ return cell;
26
+ }
27
+ const headRuntimeShims = () => ({
28
+ track: (init) => headCell(init),
29
+ derived: (fn) => headCell(typeof fn === 'function' ? fn() : fn),
30
+ get: (cell) => cell && typeof cell === 'object' && '__vskHeadValue' in cell
31
+ ? cell.__vskHeadValue
32
+ : cell,
33
+ peek: (cell) => cell && typeof cell === 'object' && '__vskHeadValue' in cell
34
+ ? cell.__vskHeadValue
35
+ : cell,
36
+ untrack: (fn) => (typeof fn === 'function' ? fn() : fn),
37
+ });
38
+ function headEvalScope(ctx) {
39
+ // The shims come LAST: they must win over the component's real `track`/`get`
40
+ // from `__vesk`, which would otherwise allocate real cells.
41
+ return { ...ctx.scope, ...ctx.locals, ...headRuntimeShims() };
42
+ }
43
+ function evalHeadExpression(expression, ctx) {
44
+ // `{count}` must resolve to the VALUE, not the cell: rewrite tracked reads the
45
+ // same way the client codegen does, then evaluate with the shims bound.
46
+ const code = transformTracked(expression, ctx.tracked);
47
+ return tryEvalExpr(code, ctx.props, headEvalScope(ctx));
48
+ }
49
+ /**
50
+ * Evaluate `raw` with `props` plus every name in `scope` bound to its value.
51
+ *
52
+ * The head pass re-evaluates a component's top-level declarations outside the
53
+ * component's own call frame, so it has to rebuild that frame. Passing the
54
+ * component's `__vesk` bindings as named parameters is what makes imports
55
+ * visible: a docs page's `const doc = getDoc(props.params.slug)` needs
56
+ * `getDoc`, which lives in `__vesk` (the generated body destructures it from
57
+ * there), and without it the head silently degraded to empty strings.
58
+ */
59
+ function evalWithScope(raw, props, scope) {
60
+ const names = Object.keys(scope);
61
+ const fn = new Function('props', ...names, 'return (' + raw + ')');
62
+ return fn(props, ...names.map((n) => scope[n]));
63
+ }
64
+ /** Identifier reads of an ESTree expression (not property keys, not `obj.prop`). */
65
+ function identifiersOf(ast, into) {
66
+ if (!ast || typeof ast !== 'object')
67
+ return;
68
+ walk(ast, null, {
69
+ Identifier(n, context) {
70
+ const parent = context.path.at(-1);
71
+ if (parent) {
72
+ if (parent.type === 'MemberExpression' && !parent.computed && parent.property === n)
73
+ return context.next();
74
+ if (parent.type === 'Property' && parent.key === n)
75
+ return context.next();
76
+ }
77
+ into.add(n.name);
78
+ return context.next();
79
+ },
80
+ });
81
+ }
82
+ /** Every interpolated/attribute expression inside a `<Head>` subtree. */
83
+ function collectHeadExprs(node, out) {
84
+ if (node instanceof StaticNode) {
85
+ for (const child of node.children) {
86
+ if (child instanceof DynamicBinding)
87
+ out.push(child.expression);
88
+ else
89
+ collectHeadExprs(child, out);
90
+ }
91
+ }
92
+ }
93
+ /** Top-level declarations of a component: tracked cells and `const` locals. */
94
+ function declaredNames(comp) {
95
+ const out = new Map();
96
+ for (const node of comp.body) {
97
+ if (node instanceof TrackDecl) {
98
+ const cellName = node.rawName || node.name;
99
+ let initAst = null;
100
+ try {
101
+ initAst = parse(node.init);
102
+ }
103
+ catch { /* unparsable init: no deps to follow */ }
104
+ const entry = { kind: 'track', initSrc: node.init, initAst, names: [cellName] };
105
+ out.set(cellName, entry);
106
+ if (node.name !== cellName) {
107
+ out.set(node.name, entry);
108
+ entry.names.push(node.name);
109
+ }
110
+ }
111
+ else if (node instanceof RuntimeStatement && node.ast) {
112
+ const stmt = node.ast;
113
+ if (stmt.type !== 'VariableDeclaration')
114
+ continue;
115
+ for (const decl of stmt.declarations) {
116
+ if (decl.id.type !== 'Identifier' || !decl.init || !node.source)
117
+ continue;
118
+ out.set(decl.id.name, {
119
+ kind: 'const',
120
+ initSrc: node.source.slice(decl.init.start, decl.init.end),
121
+ initAst: decl.init,
122
+ names: [decl.id.name],
123
+ });
124
+ }
125
+ }
126
+ }
127
+ return out;
128
+ }
129
+ /**
130
+ * The transitive closure of top-level names the `<Head>` block reads.
131
+ *
132
+ * THE head pass must evaluate only what the head uses. Evaluating every
133
+ * top-level declaration (which is what it used to do) means re-running the
134
+ * component's SIDE EFFECTS in the rebuilt frame: a page whose body does
135
+ * `const data = useFetch('/api/fail')` fetched twice per render, because the
136
+ * head pass called `useFetch` a second time now that the runtime names are in
137
+ * scope. Imports and tracked reads are still resolved — just the declarations
138
+ * the head actually needs, plus the ones those depend on.
139
+ */
140
+ function headNeededNames(comp) {
141
+ const wanted = new Set();
142
+ for (const node of comp.body) {
143
+ if (!(node instanceof HeadBlock))
144
+ continue;
145
+ const exprs = [];
146
+ for (const child of node.children)
147
+ collectHeadExprs(child, exprs);
148
+ for (const e of exprs)
149
+ identifiersOf(e.ast, wanted);
150
+ }
151
+ const declared = declaredNames(comp);
152
+ const needed = new Set();
153
+ const queue = [...wanted].filter((n) => declared.has(n));
154
+ while (queue.length > 0) {
155
+ const name = queue.shift();
156
+ if (needed.has(name))
157
+ continue;
158
+ needed.add(name);
159
+ const entry = declared.get(name);
160
+ if (!entry)
161
+ continue;
162
+ const deps = new Set();
163
+ identifiersOf(entry.initAst, deps);
164
+ for (const d of deps) {
165
+ // A tracked init reads other cells through the runtime, so follow the
166
+ // tracked names the init mentions.
167
+ if (declared.has(d) && !needed.has(d))
168
+ queue.push(d);
169
+ }
170
+ }
171
+ return needed;
172
+ }
173
+ function evaluateLocals(comp, props, scope = {}, tracked = new Map(), needed = null) {
15
174
  const locals = {};
175
+ const wanted = (name) => needed === null || needed.has(name);
16
176
  for (const node of comp.body) {
177
+ if (node instanceof TrackDecl) {
178
+ // `const [count, setCount] = track(0)`: bind every name the declaration
179
+ // introduces to a head cell, so a later local or head expression that
180
+ // reads it sees the current value through the shims.
181
+ const cellName = node.rawName || node.name;
182
+ if (!wanted(cellName) && !wanted(node.name))
183
+ continue;
184
+ const init = transformTrackedInit(node.init, tracked);
185
+ let value;
186
+ try {
187
+ value = evalWithScope(init, props, { ...scope, ...locals, ...headRuntimeShims() });
188
+ }
189
+ catch {
190
+ value = undefined;
191
+ }
192
+ const cell = headCell(value);
193
+ if (node.name !== cellName)
194
+ locals[node.name] = cell;
195
+ locals[cellName] = cell;
196
+ continue;
197
+ }
17
198
  if (node instanceof RuntimeStatement && node.ast) {
18
199
  const stmt = node.ast;
19
200
  if (stmt.type === 'VariableDeclaration') {
20
201
  for (const decl of stmt.declarations) {
21
202
  if (decl.id.type === 'Identifier' && decl.init && node.source) {
22
203
  const name = decl.id.name;
204
+ if (!wanted(name))
205
+ continue;
23
206
  const initSrc = node.source.slice(decl.init.start, decl.init.end);
24
207
  try {
25
- const fn = new Function('props', 'return (' + initSrc + ')');
26
- locals[name] = fn(props);
208
+ locals[name] = evalWithScope(initSrc, props, {
209
+ ...scope,
210
+ ...locals,
211
+ ...headRuntimeShims(),
212
+ });
27
213
  }
28
214
  catch {
29
- // expression can't be evaluated — skip
215
+ // expression can't be evaluated — skip (its dependents degrade
216
+ // to empty, exactly as they did before the scope was passed)
30
217
  }
31
218
  }
32
219
  }
@@ -35,7 +222,7 @@ function evaluateLocals(comp, props) {
35
222
  }
36
223
  return locals;
37
224
  }
38
- function headElementKey(node, props, locals) {
225
+ function headElementKey(node, ctx) {
39
226
  if (!(node instanceof StaticNode))
40
227
  return null;
41
228
  const tag = node.tag;
@@ -47,7 +234,7 @@ function headElementKey(node, props, locals) {
47
234
  for (const child of node.children) {
48
235
  if (child instanceof DynamicBinding && child.kind === 'attribute' && child.target && child.target !== 'ref') {
49
236
  try {
50
- attrMap.set(child.target, String(tryEvalExpr(child.expression.raw, props, locals)));
237
+ attrMap.set(child.target, String(evalHeadExpression(child.expression, ctx)));
51
238
  }
52
239
  catch { /* skip */ }
53
240
  }
@@ -88,14 +275,13 @@ function headIsReconcilable(tag, attrMap) {
88
275
  return false;
89
276
  return true;
90
277
  }
91
- function irNodeToHeadHtml(node, props, locals = {}, reconcileable = false) {
278
+ function irNodeToHeadHtml(node, ctx, reconcileable = false) {
92
279
  if (node instanceof StaticNode) {
93
280
  const attrMap = new Map(node.attributes.map((a) => [a.name, a.value]));
94
281
  for (const child of node.children) {
95
282
  if (child instanceof DynamicBinding && child.kind === 'attribute' && child.target && child.target !== 'ref') {
96
283
  try {
97
- const val = tryEvalExpr(child.expression.raw, props, locals);
98
- attrMap.set(child.target, String(val));
284
+ attrMap.set(child.target, String(evalHeadExpression(child.expression, ctx)));
99
285
  }
100
286
  catch { /* skip */ }
101
287
  }
@@ -108,7 +294,7 @@ function irNodeToHeadHtml(node, props, locals = {}, reconcileable = false) {
108
294
  return `<${node.tag}${attrs}${marker} />`;
109
295
  const inner = node.children
110
296
  .filter((c) => !(c instanceof DynamicBinding && c.kind === 'attribute' && c.target !== 'ref'))
111
- .map((c) => irNodeToHeadHtml(c, props, locals, false))
297
+ .map((c) => irNodeToHeadHtml(c, ctx, false))
112
298
  .join('');
113
299
  return `<${node.tag}${attrs}${marker}>${inner}</${node.tag}>`;
114
300
  }
@@ -116,8 +302,7 @@ function irNodeToHeadHtml(node, props, locals = {}, reconcileable = false) {
116
302
  return node.value;
117
303
  if (node instanceof DynamicBinding) {
118
304
  try {
119
- const val = tryEvalExpr(node.expression.raw, props, locals);
120
- return escapeHtml(String(val));
305
+ return escapeHtml(String(evalHeadExpression(node.expression, ctx)));
121
306
  }
122
307
  catch {
123
308
  return '';
@@ -125,19 +310,35 @@ function irNodeToHeadHtml(node, props, locals = {}, reconcileable = false) {
125
310
  }
126
311
  return '';
127
312
  }
128
- export function renderHeadHtml(comp, props = {}) {
129
- const locals = evaluateLocals(comp, props);
313
+ /**
314
+ * Serialize one component's `<Head>` block.
315
+ *
316
+ * `scope` is the component's `__vesk` bindings (imports, runtime names, the
317
+ * sub-components its body destructures). The head is assembled by re-walking
318
+ * the IR and re-evaluating the expressions rather than by capturing the real
319
+ * render, so the re-evaluation needs the same bindings the component had —
320
+ * without them every expression that touched an import threw and was swallowed,
321
+ * and the served document shipped an empty `<title>`/`<meta>` while the page
322
+ * body rendered fine (and the client corrected it on hydration, which is why
323
+ * this only ever showed up in view-source, in crawlers, and after an SPA nav
324
+ * re-applied the server head).
325
+ */
326
+ export function renderHeadHtml(comp, props = {}, scope = {}) {
327
+ const tracked = collectTrackedNames(comp.body);
328
+ const needed = headNeededNames(comp);
329
+ const locals = evaluateLocals(comp, props, scope, tracked, needed);
330
+ const ctx = { props, locals, tracked, scope };
130
331
  const seen = new Set();
131
332
  const parts = [];
132
333
  for (const node of comp.body) {
133
334
  if (node instanceof HeadBlock) {
134
335
  for (const child of node.children) {
135
- const key = headElementKey(child, props, locals);
336
+ const key = headElementKey(child, ctx);
136
337
  if (key !== null && seen.has(key))
137
338
  continue;
138
339
  if (key !== null)
139
340
  seen.add(key);
140
- parts.push(irNodeToHeadHtml(child, props, locals, true));
341
+ parts.push(irNodeToHeadHtml(child, ctx, true));
141
342
  }
142
343
  }
143
344
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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"}
@@ -187,7 +187,9 @@ export function renderPage(source, componentName, props = {}, registry = new Map
187
187
  }
188
188
  return {
189
189
  body: bodyHtml,
190
- head: renderHeadHtml(targetComp, ssrProps),
190
+ // The component's own scope, so head expressions that read an import
191
+ // resolve (see renderHeadHtml).
192
+ head: renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk)),
191
193
  props: ssrProps
192
194
  };
193
195
  })();
@@ -199,7 +201,7 @@ export function renderPage(source, componentName, props = {}, registry = new Map
199
201
  finally {
200
202
  delete globalThis.__vsk_ssr;
201
203
  }
202
- const headHtml = targetComp ? renderHeadHtml(targetComp, ssrProps) : '';
204
+ const headHtml = targetComp ? renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk)) : '';
203
205
  const pending = globalThis[`__vsk_ssr_promises_${renderToken}`];
204
206
  if (pending && pending.length > 0) {
205
207
  // A sync component still started server resources (useFetch): settle them
@@ -616,7 +618,7 @@ export function renderPageStream(source, componentName, props = {}, registry = n
616
618
  }
617
619
  let streamHeadHtml = null;
618
620
  if (targetComp) {
619
- let headHtml = renderHeadHtml(targetComp, ssrProps);
621
+ let headHtml = renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk));
620
622
  if (options.pageHead) {
621
623
  const merged = mergeHeadHtml(options.pageHead, headHtml);
622
624
  headHtml = merged.html;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
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.47",
23
+ "@vesk/types": "^0.2.48",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",