@vesk/compiler 0.2.50-canary.1 → 0.2.50-canary.11

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":"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"}
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,CAqT9E"}
@@ -1610,6 +1610,13 @@ export function generateIR(ast, source, filename) {
1610
1610
  const importedNames = new Set();
1611
1611
  let staticProps = null;
1612
1612
  let loadFn = null;
1613
+ // `export const metadata = defineMetadata({...})` — the SOURCE of the
1614
+ // expression, captured like `load`/`getStaticProps`. It is evaluated in a
1615
+ // sandbox with nothing in scope but `defineMetadata`, so a static head can
1616
+ // never depend on the component's frame evaluating correctly (the class of
1617
+ // bug the `<Head>` interpolation path is prone to) and can never be
1618
+ // non-deterministic.
1619
+ let metadataSource = null;
1613
1620
  const topLevelCode = [];
1614
1621
  const exportAliases = [];
1615
1622
  const reexportSources = [];
@@ -1660,6 +1667,17 @@ export function generateIR(ast, source, filename) {
1660
1667
  continue;
1661
1668
  }
1662
1669
  }
1670
+ if (node.type === 'ExportNamedDeclaration' && node.declaration && !metadataSource) {
1671
+ const decl = node.declaration;
1672
+ const first = decl.type === 'VariableDeclaration' ? decl.declarations[0] : null;
1673
+ if (first?.id?.name === 'metadata') {
1674
+ // The INITIALIZER, not the declaration: the sandbox evaluates this as
1675
+ // an expression (`return ( … )`), so a `const metadata = …` wrapper
1676
+ // would be a syntax error.
1677
+ metadataSource = first.init ? stripCodeTypes(getSource(source, first.init)) : null;
1678
+ continue;
1679
+ }
1680
+ }
1663
1681
  if (node.type === 'ExportNamedDeclaration' && node.declaration && !loadFn) {
1664
1682
  const decl = node.declaration;
1665
1683
  const fnName = decl.type === 'FunctionDeclaration'
@@ -1890,7 +1908,9 @@ export function generateIR(ast, source, filename) {
1890
1908
  imports.push(`import { ${missing.join(', ')} } from '@vesk/runtime';`);
1891
1909
  }
1892
1910
  }
1893
- return new IRRoot(components, imports, importedNames, staticProps, loadFn, topLevelCode, exportAliases, reexportSources);
1911
+ const irRoot = new IRRoot(components, imports, importedNames, staticProps, loadFn, topLevelCode, exportAliases, reexportSources);
1912
+ irRoot.metadataSource = metadataSource;
1913
+ return irRoot;
1894
1914
  }
1895
1915
  finally {
1896
1916
  currentVskNamespaceLocals = prevNamespaceLocals;
package/dist/ir.d.ts CHANGED
@@ -12,6 +12,13 @@ export declare class IRRoot {
12
12
  importedNames: Set<string>;
13
13
  staticProps: string | null;
14
14
  loadFn: string | null;
15
+ /**
16
+ * Source of `export const metadata = defineMetadata({...})`, when present.
17
+ * Static by construction: evaluated with only `defineMetadata` in scope, at
18
+ * build time where possible and in a sealed sandbox at request time
19
+ * otherwise, so the head never depends on the component's frame.
20
+ */
21
+ metadataSource?: string | null;
15
22
  topLevelCode: string[];
16
23
  /**
17
24
  * Same-file specifier exports (`export { A }`, `export { A as B }`) as
package/dist/ir.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ir.d.ts","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ir.d.ts","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"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"}
package/dist/ir.js CHANGED
@@ -16,6 +16,13 @@ export class IRRoot {
16
16
  importedNames;
17
17
  staticProps;
18
18
  loadFn;
19
+ /**
20
+ * Source of `export const metadata = defineMetadata({...})`, when present.
21
+ * Static by construction: evaluated with only `defineMetadata` in scope, at
22
+ * build time where possible and in a sealed sandbox at request time
23
+ * otherwise, so the head never depends on the component's frame.
24
+ */
25
+ metadataSource;
19
26
  topLevelCode;
20
27
  /**
21
28
  * Same-file specifier exports (`export { A }`, `export { A as B }`) as
@@ -74,6 +74,31 @@ export declare function resolveImportPath(spec: string, fromDir: string): string
74
74
  * `node_modules`) share one cache entry and one module instance.
75
75
  */
76
76
  export declare function resolveSsrModule(specifier: string, fromDir: string): string | null;
77
+ /**
78
+ * Resolve a subpath through a package's `exports` map.
79
+ *
80
+ * This is what makes a Vesk app work inside an existing pnpm/npm/yarn
81
+ * workspace. A modern workspace package declares `exports` and often NO `main`
82
+ * (or points `main` at a CJS build while the app imports ESM), and subpath
83
+ * imports (`@acme/ui/button`) resolve through the map rather than through the
84
+ * directory layout. Without this, such a package simply does not resolve, and
85
+ * the app has to vendor it.
86
+ *
87
+ * Conditions are tried in import order: `import`, `module`, `node`, `default`.
88
+ * A pattern (`./dist/*.js`) is matched with the single `*` capture.
89
+ */
90
+ export declare function resolveExports(exportsField: unknown, subpath: string): string | null;
91
+ /**
92
+ * Resolve a bare specifier through a package's `module` field (the ESM entry
93
+ * in a package that also has a CJS `main`).
94
+ */
95
+ export declare function resolveViaPackageModuleField(specifier: string, fromDir: string): string | null;
96
+ /**
97
+ * Resolve a bare specifier through its package's `exports` map, walking
98
+ * node_modules upwards. Returns null when the package has no map, does not
99
+ * export the subpath, or cannot be read.
100
+ */
101
+ export declare function resolveViaPackageExports(specifier: string, fromDir: string): string | null;
77
102
  /** Rewrites an ESM program body (imports/exports removed) into CJS statements. */
78
103
  export declare function esmToCjs(body: Array<{
79
104
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"module-imports.d.ts","sourceRoot":"","sources":["../src/module-imports.ts"],"names":[],"mappings":"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"}
@@ -900,6 +900,21 @@ export function resolveSsrModule(specifier, fromDir) {
900
900
  const aliased = resolveAliasModule(specifier, fromDir);
901
901
  if (aliased)
902
902
  return toRealPath(aliased);
903
+ // Bare specifier. A package's `exports` map is consulted BEFORE Node's own
904
+ // resolver: `createRequire.resolve` applies the CJS conditions, so it picks
905
+ // a `.cjs` build for a package that also ships ESM — and fails outright on
906
+ // an `import`-only package. Vesk emits ESM, so the `import` condition is
907
+ // the correct one. Node's resolver stays as the fallback (builtins, exotic
908
+ // conditions), and the node_modules walk after that.
909
+ const viaExports = resolveViaPackageExports(specifier, fromDir);
910
+ if (viaExports)
911
+ return viaExports;
912
+ // Same reasoning for the legacy fields: a package declaring both `module`
913
+ // and `main` wants the ESM build (Vesk emits ESM), while `createRequire`
914
+ // would take `main` and hand us the CJS one.
915
+ const viaModuleField = resolveViaPackageModuleField(specifier, fromDir);
916
+ if (viaModuleField)
917
+ return viaModuleField;
903
918
  // Bare specifier — prefer the native resolver (exports map, conditions,
904
919
  // builtins, symlinks), then fall back to a node_modules walk-up.
905
920
  const native = nativeResolve(specifier, fromDir);
@@ -983,6 +998,145 @@ function statOrNull(p) {
983
998
  return null;
984
999
  }
985
1000
  }
1001
+ /**
1002
+ * Resolve a subpath through a package's `exports` map.
1003
+ *
1004
+ * This is what makes a Vesk app work inside an existing pnpm/npm/yarn
1005
+ * workspace. A modern workspace package declares `exports` and often NO `main`
1006
+ * (or points `main` at a CJS build while the app imports ESM), and subpath
1007
+ * imports (`@acme/ui/button`) resolve through the map rather than through the
1008
+ * directory layout. Without this, such a package simply does not resolve, and
1009
+ * the app has to vendor it.
1010
+ *
1011
+ * Conditions are tried in import order: `import`, `module`, `node`, `default`.
1012
+ * A pattern (`./dist/*.js`) is matched with the single `*` capture.
1013
+ */
1014
+ export function resolveExports(exportsField, subpath) {
1015
+ const conditions = ['import', 'module', 'node', 'default'];
1016
+ const target = (value) => {
1017
+ if (typeof value === 'string')
1018
+ return value;
1019
+ if (!value || typeof value !== 'object')
1020
+ return null;
1021
+ for (const cond of conditions) {
1022
+ if (cond in value) {
1023
+ const got = target(value[cond]);
1024
+ if (got)
1025
+ return got;
1026
+ }
1027
+ }
1028
+ return null;
1029
+ };
1030
+ if (typeof exportsField === 'string')
1031
+ return subpath === '.' ? exportsField : null;
1032
+ if (!exportsField || typeof exportsField !== 'object')
1033
+ return null;
1034
+ const map = exportsField;
1035
+ if (subpath in map) {
1036
+ const direct = target(map[subpath]);
1037
+ if (direct)
1038
+ return direct;
1039
+ return null;
1040
+ }
1041
+ // Longest-prefix match for `./*` patterns, as Node does.
1042
+ let best = null;
1043
+ for (const key of Object.keys(map)) {
1044
+ const star = key.indexOf('*');
1045
+ if (star === -1)
1046
+ continue;
1047
+ const prefix = key.slice(0, star);
1048
+ const suffix = key.slice(star + 1);
1049
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
1050
+ continue;
1051
+ if (subpath.length < prefix.length + suffix.length)
1052
+ continue;
1053
+ if (best === null || prefix.length > best.key.length) {
1054
+ best = { key: prefix, star: subpath.slice(prefix.length, subpath.length - suffix.length) };
1055
+ }
1056
+ }
1057
+ if (!best)
1058
+ return null;
1059
+ const pattern = target(map[Object.keys(map).find((k) => k.startsWith(best.key + '*') && k.endsWith(best.key.slice(0, -1)) + '*')] ?? map[`${best.key}*`]);
1060
+ if (pattern === null)
1061
+ return null;
1062
+ return pattern.split('*').join(best.star);
1063
+ }
1064
+ /**
1065
+ * Resolve a bare specifier through a package's `module` field (the ESM entry
1066
+ * in a package that also has a CJS `main`).
1067
+ */
1068
+ export function resolveViaPackageModuleField(specifier, fromDir) {
1069
+ const firstSlash = specifier.indexOf('/');
1070
+ const splitAt = specifier.startsWith('@') ? specifier.indexOf('/', firstSlash + 1) : firstSlash;
1071
+ const pkgName = splitAt > 0 ? specifier.slice(0, splitAt) : specifier;
1072
+ if (splitAt > 0)
1073
+ return null; // a subpath import needs the exports map
1074
+ let dir = fromDir;
1075
+ for (let depth = 0; depth < 64; depth++) {
1076
+ const pkgPath = join(dir, 'node_modules', pkgName, 'package.json');
1077
+ if (statOrNull(pkgPath) !== null) {
1078
+ try {
1079
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
1080
+ if (typeof pkg.module === 'string' && pkg.module.length > 0) {
1081
+ const found = probeFile(resolve(join(dir, 'node_modules', pkgName), pkg.module));
1082
+ if (found)
1083
+ return toRealPath(found);
1084
+ }
1085
+ }
1086
+ catch {
1087
+ // malformed package.json — fall through
1088
+ }
1089
+ return null;
1090
+ }
1091
+ const parent = dirname(dir);
1092
+ if (parent === dir)
1093
+ break;
1094
+ dir = parent;
1095
+ }
1096
+ return null;
1097
+ }
1098
+ /**
1099
+ * Resolve a bare specifier through its package's `exports` map, walking
1100
+ * node_modules upwards. Returns null when the package has no map, does not
1101
+ * export the subpath, or cannot be read.
1102
+ */
1103
+ export function resolveViaPackageExports(specifier, fromDir) {
1104
+ // A scoped name (`@acme/ui`) has its slash INSIDE the package name — taking
1105
+ // the first slash as the subpath separator turns it into package `@acme` +
1106
+ // subpath `./ui`, which no `exports` map matches, and the lookup silently
1107
+ // falls through to Node's resolver.
1108
+ const firstSlash = specifier.indexOf('/');
1109
+ const splitAt = specifier.startsWith('@') ? specifier.indexOf('/', firstSlash + 1) : firstSlash;
1110
+ const pkgName = splitAt > 0 ? specifier.slice(0, splitAt) : specifier;
1111
+ const subpath = splitAt > 0 ? '.' + specifier.slice(splitAt) : '.';
1112
+ let dir = fromDir;
1113
+ for (let depth = 0; depth < 64; depth++) {
1114
+ const pkgDir = join(dir, 'node_modules', pkgName);
1115
+ const pkgPath = join(pkgDir, 'package.json');
1116
+ if (statOrNull(pkgPath) !== null) {
1117
+ try {
1118
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
1119
+ if (pkg.exports !== undefined) {
1120
+ const target = resolveExports(pkg.exports, subpath);
1121
+ if (target) {
1122
+ const found = probeFile(resolve(pkgDir, target));
1123
+ if (found)
1124
+ return toRealPath(found);
1125
+ }
1126
+ }
1127
+ }
1128
+ catch {
1129
+ // malformed package.json — fall through to the resolvers below
1130
+ }
1131
+ return null;
1132
+ }
1133
+ const parent = dirname(dir);
1134
+ if (parent === dir)
1135
+ break;
1136
+ dir = parent;
1137
+ }
1138
+ return null;
1139
+ }
986
1140
  function probeFile(base) {
987
1141
  const st = statOrNull(base);
988
1142
  if (st && st.isFile())
@@ -993,10 +1147,24 @@ function probeFile(base) {
993
1147
  if (pkgSt && pkgSt.isFile()) {
994
1148
  try {
995
1149
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
996
- if (typeof pkg.main === 'string' && pkg.main.length > 0) {
997
- const viaMain = probeFile(resolve(base, pkg.main));
998
- if (viaMain)
999
- return viaMain;
1150
+ // `exports` first: it is what the package author intends and what a
1151
+ // workspace package usually ships. `main`/`module` are the legacy
1152
+ // fallbacks.
1153
+ if (pkg.exports !== undefined) {
1154
+ const viaExports = resolveExports(pkg.exports, '.');
1155
+ if (viaExports) {
1156
+ const found = probeFile(resolve(base, viaExports));
1157
+ if (found)
1158
+ return found;
1159
+ }
1160
+ }
1161
+ for (const field of ['module', 'main']) {
1162
+ const value = pkg[field];
1163
+ if (typeof value === 'string' && value.length > 0) {
1164
+ const found = probeFile(resolve(base, value));
1165
+ if (found)
1166
+ return found;
1167
+ }
1000
1168
  }
1001
1169
  }
1002
1170
  catch {
@@ -27,6 +27,14 @@ export interface PrecompileFilePlan {
27
27
  topLevelCode: string[];
28
28
  /** The file's `.vsk` imports, precompiled recursively. */
29
29
  subFiles: PrecompileSubFilePlan[];
30
+ /**
31
+ * Source of a static `export const metadata = defineMetadata({...})`.
32
+ *
33
+ * Carried as SOURCE, not as a value, so the plan stays pure JSON (it is
34
+ * embedded in a generated function and can be cached to disk). Evaluation
35
+ * happens in a sealed sandbox with only `defineMetadata` in scope.
36
+ */
37
+ metadataSource?: string;
30
38
  }
31
39
  /**
32
40
  * Recursively JSON-serializes an IR root. IR class instances are tagged with
@@ -1 +1 @@
1
- {"version":3,"file":"precompile-runtime.d.ts","sourceRoot":"","sources":["../src/precompile-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AA0BlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAe9F,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACnB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,2CAA2C;IAC3C,UAAU,EAAE,yBAAyB,EAAE,CAAC;IACxC,iEAAiE;IACjE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAqCD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiBhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA0BlD;AAgHD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,CAE7E"}
1
+ {"version":3,"file":"precompile-runtime.d.ts","sourceRoot":"","sources":["../src/precompile-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AA0BlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAe9F,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACnB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,2CAA2C;IAC3C,UAAU,EAAE,yBAAyB,EAAE,CAAC;IACxC,iEAAiE;IACjE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,6EAA6E;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAqCD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiBhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA0BlD;AAgHD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,CAE7E"}
@@ -74,6 +74,7 @@ function precompileFileInternal(source, sourcePath, seenImportFiles) {
74
74
  setVskHydrate(false);
75
75
  }
76
76
  const topLevelCode = transformTopLevelForActions(ir.topLevelCode, 'server');
77
+ const metadataSource = ir.metadataSource || null;
77
78
  const runtimeImports = ir.imports.slice();
78
79
  const collector = { keysByAbs: new Map(), modules: [] };
79
80
  const bindings = sourcePath !== undefined ? collectModuleBundle(ir.imports, dirname(sourcePath), collector) : [];
@@ -105,5 +106,8 @@ function precompileFileInternal(source, sourcePath, seenImportFiles) {
105
106
  bindings,
106
107
  topLevelCode,
107
108
  subFiles,
109
+ // A static `export const metadata` rides along so a serverless function can
110
+ // serialize the head without the `.vsk` source being present at all.
111
+ ...(metadataSource ? { metadataSource } : {}),
108
112
  };
109
113
  }
package/dist/scan.d.ts CHANGED
@@ -93,6 +93,21 @@ export declare function hasTopLevelComma(text: string): boolean;
93
93
  * and `for (... in ...)` headers apart from classic `for` loops.
94
94
  */
95
95
  export declare function containsForOfIn(text: string): boolean;
96
+ /**
97
+ * Reports whether `ident` appears in `text` as a CALLED identifier — i.e.
98
+ * followed by an optional TS generic clause and then `(`.
99
+ *
100
+ * This is a character-loop tokenizer, not a text match: it steps over
101
+ * whitespace, `//` and block comments, and quoted / template strings, so
102
+ * `track(` appearing inside a string, a comment or a property access
103
+ * (`obj.track(...)`) never counts. Template literals are skipped whole —
104
+ * including their `${...}` holes — because a `track(` in an interpolation is
105
+ * not a reliable enough signal to inject a runtime binding for.
106
+ *
107
+ * Used to decide which runtime helpers a raw statement needs without
108
+ * requiring the caller to have been lowered into a TrackDecl first.
109
+ */
110
+ export declare function callsIdentifier(text: string, ident: string): boolean;
96
111
  /**
97
112
  * Extracts the argument text of a `track<...>(...)` call from an init
98
113
  * expression. Handles nested generics (`track<Map<string, number>>(...)`)
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAsB1D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmDlD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAkBvE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBrD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAqB9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAexD"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAsB1D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmDlD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAkBvE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAwDpE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAqB9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAexD"}
package/dist/scan.js CHANGED
@@ -408,6 +408,70 @@ export function containsForOfIn(text) {
408
408
  }
409
409
  return false;
410
410
  }
411
+ /**
412
+ * Reports whether `ident` appears in `text` as a CALLED identifier — i.e.
413
+ * followed by an optional TS generic clause and then `(`.
414
+ *
415
+ * This is a character-loop tokenizer, not a text match: it steps over
416
+ * whitespace, `//` and block comments, and quoted / template strings, so
417
+ * `track(` appearing inside a string, a comment or a property access
418
+ * (`obj.track(...)`) never counts. Template literals are skipped whole —
419
+ * including their `${...}` holes — because a `track(` in an interpolation is
420
+ * not a reliable enough signal to inject a runtime binding for.
421
+ *
422
+ * Used to decide which runtime helpers a raw statement needs without
423
+ * requiring the caller to have been lowered into a TrackDecl first.
424
+ */
425
+ export function callsIdentifier(text, ident) {
426
+ let i = 0;
427
+ const len = text.length;
428
+ // True when the previous significant character means the next identifier
429
+ // is a member access (`obj.track`) rather than a free call.
430
+ let afterDot = false;
431
+ while (i < len) {
432
+ const ch = text[i];
433
+ if (isWhitespaceChar(ch)) {
434
+ i++;
435
+ continue;
436
+ }
437
+ const commentEnd = skipComment(text, i);
438
+ if (commentEnd !== i) {
439
+ i = commentEnd;
440
+ continue;
441
+ }
442
+ if (ch === '"' || ch === "'" || ch === '`') {
443
+ i = skipString(text, i);
444
+ afterDot = false;
445
+ continue;
446
+ }
447
+ if (ch === '.' || (ch === '?' && text[i + 1] === '.')) {
448
+ afterDot = true;
449
+ i += ch === '.' ? 1 : 2;
450
+ continue;
451
+ }
452
+ if (isIdentStartCode(ch.charCodeAt(0))) {
453
+ let j = i + 1;
454
+ while (j < len && isIdentCharCode(text.charCodeAt(j)))
455
+ j++;
456
+ const name = text.slice(i, j);
457
+ let k = skipWhitespace(text, j);
458
+ // A `name<T,>(...)` generic call — skip the balanced `<...>`.
459
+ if (text[k] === '<') {
460
+ k = skipTrackGeneric(text, k);
461
+ k = skipWhitespace(text, k);
462
+ }
463
+ if (name === ident && text[k] === '(' && !afterDot) {
464
+ return true;
465
+ }
466
+ afterDot = false;
467
+ i = j;
468
+ continue;
469
+ }
470
+ afterDot = false;
471
+ i++;
472
+ }
473
+ return false;
474
+ }
411
475
  /**
412
476
  * Extracts the argument text of a `track<...>(...)` call from an init
413
477
  * expression. Handles nested generics (`track<Map<string, number>>(...)`)
@@ -1,4 +1,19 @@
1
1
  import type { ComponentIR } from '@vesk/compiler/src/ir';
2
+ /**
3
+ * Evaluate a static `export const metadata = …` in a SEALED sandbox.
4
+ *
5
+ * The sandbox has exactly one binding — `defineMetadata` — and no access to
6
+ * globals, the request, or the component. That is the whole point: a static
7
+ * head must not be able to depend on the component's frame evaluating
8
+ * correctly (which is how `<title>{doc.title}</title>` came out empty), and it
9
+ * must not be able to be non-deterministic. Anything that needs state belongs
10
+ * in `<Head>`, which is reactive.
11
+ *
12
+ * Returns null — never throws — for a source that will not evaluate, so a
13
+ * broken metadata declaration degrades to "no static head" rather than taking
14
+ * the page down.
15
+ */
16
+ export declare function renderStaticMetadataHtml(source: string | null | undefined): string;
2
17
  /**
3
18
  * Serialize one component's `<Head>` block.
4
19
  *
@@ -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;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
+ {"version":3,"file":"server-head.d.ts","sourceRoot":"","sources":["../src/server-head.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAiVzD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAYlF;AAED;;;;;;;;;;;;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"}
@@ -3,6 +3,7 @@ import { tryEvalExpr, escapeHtml } from '@vesk/compiler/src/server-utils';
3
3
  import { isWhitespaceChar } from '@vesk/compiler/src/scan';
4
4
  import { parse } from '@vesk/compiler/src/parser';
5
5
  import { collectTrackedNames, transformTracked, transformTrackedInit } from '@vesk/compiler/src/client-codegen';
6
+ import { defineMetadata as metadataShim, metadataToHtml } from '@vesk/runtime/src/metadata';
6
7
  import { walk } from 'zimmerframe';
7
8
  function isHtmlNameChar(ch) {
8
9
  const code = ch.charCodeAt(0);
@@ -310,6 +311,35 @@ function irNodeToHeadHtml(node, ctx, reconcileable = false) {
310
311
  }
311
312
  return '';
312
313
  }
314
+ /**
315
+ * Evaluate a static `export const metadata = …` in a SEALED sandbox.
316
+ *
317
+ * The sandbox has exactly one binding — `defineMetadata` — and no access to
318
+ * globals, the request, or the component. That is the whole point: a static
319
+ * head must not be able to depend on the component's frame evaluating
320
+ * correctly (which is how `<title>{doc.title}</title>` came out empty), and it
321
+ * must not be able to be non-deterministic. Anything that needs state belongs
322
+ * in `<Head>`, which is reactive.
323
+ *
324
+ * Returns null — never throws — for a source that will not evaluate, so a
325
+ * broken metadata declaration degrades to "no static head" rather than taking
326
+ * the page down.
327
+ */
328
+ export function renderStaticMetadataHtml(source) {
329
+ if (!source)
330
+ return '';
331
+ try {
332
+ // Imported lazily and by module path so this stays a server-side concern
333
+ // and does not pull the runtime into a client bundle.
334
+ const fn = new Function('defineMetadata', 'return (' + source + ')');
335
+ const meta = fn(metadataShim);
336
+ return metadataToHtml(meta);
337
+ }
338
+ catch (e) {
339
+ console.error('vesk: static metadata could not be evaluated:', e instanceof Error ? e.message : e);
340
+ return '';
341
+ }
342
+ }
313
343
  /**
314
344
  * Serialize one component's `<Head>` block.
315
345
  *
@@ -1 +1 @@
1
- {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAoDrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAgEjJ;AA4XD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAqD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkCzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
1
+ {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AA4DrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CA4EjJ;AAyZD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAwD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAuFD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkCzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
@@ -1,8 +1,15 @@
1
- import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
1
+ import { Expression, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
2
2
  import { isStaticIR, collectTrackedNames, transformTracked, transformTrackedInit, semicolonizeStatement } from '@vesk/compiler/src/client-codegen';
3
- import { unwrapTrackCall, stripTrackGeneric, hasTopLevelComma, skipWhitespace, findBalancedEnd, startsWithIdentifier } from '@vesk/compiler/src/scan';
3
+ import { unwrapTrackCall, stripTrackGeneric, hasTopLevelComma, skipWhitespace, findBalancedEnd, startsWithIdentifier, callsIdentifier } from '@vesk/compiler/src/scan';
4
4
  import { escapeHtml, indent, exprJS, extractTopLevelNames, extractRuntimeNames, buildParamInit, __vskHydrate, __vskMarkerless, __vskImportedNames, setVskImportedNames, setVskForceClaim, takeVskForceClaim, nextVskId, } from '@vesk/compiler/src/server-utils';
5
5
  import { localValueImportNames } from '@vesk/compiler/src/module-imports';
6
+ import { lowerJsxInExpression } from '@vesk/compiler/src/server-jsx-lowering';
7
+ import { print } from 'esrap';
8
+ import tsPrinter from 'esrap/languages/ts';
9
+ /** Print an already-lowered AST with the plain TS printer (no JSX remains). */
10
+ function printLoweredTs(ast) {
11
+ return print(ast, tsPrinter()).code;
12
+ }
6
13
  import { vskImportValueNames } from '@vesk/compiler/src/vsk-imports';
7
14
  // Hydrate-mode component-boundary wrapper. Each server-rendered component call
8
15
  // is preceded by a `<!--vsk-->` marker and a single container element: the
@@ -107,8 +114,17 @@ export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
107
114
  `})();`,
108
115
  ].join('\n');
109
116
  }
110
- if (node instanceof RuntimeStatement)
117
+ if (node instanceof RuntimeStatement) {
118
+ const stmt = node;
119
+ const lowered = stmt.ast ? lowerJsxInExpression(stmt.ast) : null;
120
+ if (lowered && lowered !== stmt.ast && (tracked?.size ?? 0) === 0) {
121
+ return semicolonizeStatement(printLoweredTs(lowered));
122
+ }
123
+ if (lowered && lowered !== stmt.ast) {
124
+ return semicolonizeStatement(transformTracked({ ...node, ast: lowered }, tracked || new Map()));
125
+ }
111
126
  return semicolonizeStatement(transformTracked(node, tracked || new Map()));
127
+ }
112
128
  if (node instanceof SlotNode) {
113
129
  // Markerless mode drops the slot boundary comments: SSR output is plain
114
130
  // HTML and slot claims scope structurally (createLayoutSlot subject to the
@@ -133,7 +149,21 @@ function isEvent(target) {
133
149
  return !!target && target.startsWith('on') && target.length > 2;
134
150
  }
135
151
  function exprJSX(node, tracked) {
136
- return exprJS(transformTracked(node, tracked || new Map()));
152
+ // A function-valued child — `<For each={items}>{(item) => <li/>}</For>` —
153
+ // carries JSX inside an EXPRESSION, and the server has no IR node for it, so
154
+ // it used to be printed verbatim and the render threw "Unexpected token '<'".
155
+ // Lower it to the string the body emitter would have produced. Client output
156
+ // is untouched (a chunk goes through esbuild's tsx loader, so JSX is legal
157
+ // there).
158
+ const lowered = node.ast ? lowerJsxInExpression(node.ast) : null;
159
+ const didLower = lowered !== node.ast;
160
+ if (didLower && (tracked?.size ?? 0) === 0) {
161
+ // `transformTracked` returns the RAW source when there is nothing to
162
+ // rewrite, which would throw the lowering away. With no tracked names there
163
+ // are no rewrites to lose.
164
+ return exprJS(printLoweredTs(lowered));
165
+ }
166
+ return exprJS(transformTracked({ ...node, ast: lowered }, tracked || new Map()));
137
167
  }
138
168
  function staticNodeToJS(node, isAsync = false, tracked) {
139
169
  const lines = [];
@@ -461,19 +491,35 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked) {
461
491
  }
462
492
  }
463
493
  if (regularChildren.length > 0) {
464
- const childLines = [];
465
- for (const child of regularChildren) {
466
- const code = irNodeToJS(child, importedNames, isAsync, tracked);
467
- if (code)
468
- childLines.push(code);
494
+ // A single function-valued child (`<For each={items}>{(item) => …}</For>`,
495
+ // or a reference to one) must stay a FUNCTION: `For` calls it per item.
496
+ // Wrapping it in the `(() => { const __out = []; … })()` text emitter made it
497
+ // return undefined, so For rendered an empty list.
498
+ const fnType = regularChildren.length === 1 && regularChildren[0] instanceof DynamicBinding && regularChildren[0].kind === 'text'
499
+ ? regularChildren[0].expression.ast?.type
500
+ : null;
501
+ const isFnChild = fnType === 'ArrowFunctionExpression'
502
+ || fnType === 'FunctionExpression'
503
+ || fnType === 'Identifier'
504
+ || fnType === 'MemberExpression';
505
+ if (isFnChild) {
506
+ propsEntries.push(`children: ${exprJSX(regularChildren[0].expression, tracked)}`);
469
507
  }
470
- if (childLines.length > 0) {
471
- const childrenVar = `__ch${nextVskId()}`;
472
- propsEntries.push(`children: ${childrenVar}`);
473
- lines.push(`const ${childrenVar} = ${isAsync ? 'await (async ' : '('}() => {`);
474
- lines.push(`const __out = [];`);
475
- lines.push(indent(childLines.join('\n')));
476
- lines.push(`return __out.join(''); })();`);
508
+ else {
509
+ const childLines = [];
510
+ for (const child of regularChildren) {
511
+ const code = irNodeToJS(child, importedNames, isAsync, tracked);
512
+ if (code)
513
+ childLines.push(code);
514
+ }
515
+ if (childLines.length > 0) {
516
+ const childrenVar = `__ch${nextVskId()}`;
517
+ propsEntries.push(`children: ${childrenVar}`);
518
+ lines.push(`const ${childrenVar} = ${isAsync ? 'await (async ' : '('}() => {`);
519
+ lines.push(`const __out = [];`);
520
+ lines.push(indent(childLines.join('\n')));
521
+ lines.push(`return __out.join(''); })();`);
522
+ }
477
523
  }
478
524
  }
479
525
  const propsObj = `{ ${propsEntries.join(', ')} }`;
@@ -503,10 +549,10 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked) {
503
549
  }
504
550
  const callExpr = `${awaitKw}${calleeVar}(${propsObj}, __registry, (${calleeVar}.__veskScope || __vesk))`;
505
551
  if (__vskHydrate && !__vskMarkerless) {
506
- lines.push(`__out.push(${JSON.stringify(componentMarker(compName))} + (${callExpr} || ''));`);
552
+ lines.push(`__out.push(${JSON.stringify(componentMarker(compName))} + __flat(${callExpr}));`);
507
553
  }
508
554
  else {
509
- lines.push(`__out.push(${callExpr} || '');`);
555
+ lines.push(`__out.push(__flat(${callExpr}));`);
510
556
  }
511
557
  return lines.join('\n');
512
558
  }
@@ -524,6 +570,9 @@ export function generateFunctionBody(comp, importedNames) {
524
570
  lines.push(`const __styleText = (v) => { if (typeof v === 'string') return v; if (v && typeof v === 'object') { let s = ''; for (const k in v) { const x = v[k]; if (x == null || x === false) continue; s += k.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()) + ':' + x + ';'; } return s; } return String(v); };`);
525
571
  lines.push(`const __attr = (n, v) => (v == null || v === false) ? '' : ' ' + n + '="' + __escape(n === 'style' ? __styleText(v) : String(v)) + '"';`);
526
572
  lines.push(`const raw = (s) => s == null ? '' : String(s);`);
573
+ // A component may return an ARRAY of strings (`<For>` maps its children), and
574
+ // `Array#toString` joins the rows with commas — `<li>a</li>,<li>b</li>`.
575
+ lines.push(`const __flat = (v) => Array.isArray(v) ? v.join('') : (v == null ? '' : String(v));`);
527
576
  lines.push(`const __tk = globalThis.__vsk_ssr_token || '';`);
528
577
  if (asyncMode) {
529
578
  lines.push(`const __pk = __tk ? '__vsk_ssr_promises_' + __tk : '__vsk_ssr_promises';`);
@@ -563,6 +612,105 @@ export function generateFunctionBody(comp, importedNames) {
563
612
  __currentCompName = '';
564
613
  return lines.join('\n');
565
614
  }
615
+ /**
616
+ * Whether a component body needs `track` / `get` / `set` destructured into its
617
+ * `__vesk` scope.
618
+ *
619
+ * A `TrackDecl` (`const &[n] = track(0)`) always needs them. A PLAIN
620
+ * `const n = track(0)` does not lower to a TrackDecl — it stays a raw
621
+ * `RuntimeStatement` whose source still calls `track` — and used to reach the
622
+ * server renderer unbound, taking the whole page down with
623
+ * "track is not defined". The client codegen imports these names
624
+ * unconditionally, so the server has to recognise the plain form too or the
625
+ * two halves of a file disagree about what is in scope.
626
+ */
627
+ function bodyNeedsRuntimeCells(nodes) {
628
+ for (const node of nodes) {
629
+ if (node instanceof TrackDecl)
630
+ return true;
631
+ // Raw statement / expression text: the only place a plain `track(...)`
632
+ // call can appear outside a lowered node.
633
+ if (node instanceof RuntimeStatement && callsIdentifier(node.raw, 'track'))
634
+ return true;
635
+ if (node instanceof Expression && callsIdentifier(node.raw, 'track'))
636
+ return true;
637
+ if (node instanceof ForLoop) {
638
+ if (callsIdentifier(node.init, 'track'))
639
+ return true;
640
+ if (callsIdentifier(node.update, 'track'))
641
+ return true;
642
+ if (callsIdentifier(node.condition.raw, 'track'))
643
+ return true;
644
+ if (bodyNeedsRuntimeCells(node.bodyTemplate))
645
+ return true;
646
+ }
647
+ if (node instanceof StaticNode) {
648
+ for (const attr of node.attributes) {
649
+ if (callsIdentifier(attr.value, 'track'))
650
+ return true;
651
+ }
652
+ if (bodyNeedsRuntimeCells(node.children))
653
+ return true;
654
+ }
655
+ if (node instanceof ComponentCall) {
656
+ for (const prop of node.props) {
657
+ if (callsIdentifier(prop.value.raw, 'track'))
658
+ return true;
659
+ }
660
+ for (const spread of node.spreadProps) {
661
+ if (callsIdentifier(spread.raw, 'track'))
662
+ return true;
663
+ }
664
+ if (bodyNeedsRuntimeCells(node.children))
665
+ return true;
666
+ }
667
+ if (node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
668
+ if (bodyNeedsRuntimeCells(node.children))
669
+ return true;
670
+ }
671
+ if (node instanceof PropSlot && bodyNeedsRuntimeCells(node.body))
672
+ return true;
673
+ if (node instanceof MapRegion) {
674
+ if (callsIdentifier(node.expression.raw, 'track'))
675
+ return true;
676
+ if (bodyNeedsRuntimeCells(node.bodyTemplate))
677
+ return true;
678
+ if (bodyNeedsRuntimeCells(node.alternateNodes))
679
+ return true;
680
+ }
681
+ if (node instanceof OpaqueDynamicRegion) {
682
+ if (callsIdentifier(node.condition.raw, 'track'))
683
+ return true;
684
+ if (bodyNeedsRuntimeCells(node.consequentNodes))
685
+ return true;
686
+ if (bodyNeedsRuntimeCells(node.alternateNodes))
687
+ return true;
688
+ }
689
+ if (node instanceof WhileLoop) {
690
+ if (callsIdentifier(node.condition.raw, 'track'))
691
+ return true;
692
+ if (bodyNeedsRuntimeCells(node.bodyTemplate))
693
+ return true;
694
+ }
695
+ if (node instanceof SwitchBlock) {
696
+ if (callsIdentifier(node.discriminant.raw, 'track'))
697
+ return true;
698
+ for (const c of node.cases) {
699
+ if (c.test && callsIdentifier(c.test.raw, 'track'))
700
+ return true;
701
+ if (bodyNeedsRuntimeCells(c.body))
702
+ return true;
703
+ }
704
+ }
705
+ if (node instanceof TryCatch) {
706
+ if (bodyNeedsRuntimeCells(node.bodyTemplate))
707
+ return true;
708
+ if (bodyNeedsRuntimeCells(node.catchBody))
709
+ return true;
710
+ }
711
+ }
712
+ return false;
713
+ }
566
714
  /**
567
715
  * Builds the compiled body source for every component in an IR root. Used by
568
716
  * both `buildComponentMap` (live functions) and `precompile.ts` (AOT SSR plans
@@ -579,7 +727,7 @@ export function buildComponentEntries(irRoot) {
579
727
  const componentNames = new Set(irRoot.components.map((c) => c.name));
580
728
  const topValueNames = topNames.filter((n) => !componentNames.has(n));
581
729
  const importedNames = new Set([...runtimeNames, ...localValueNames, ...topValueNames]);
582
- const hasTracked = irRoot.components.some((c) => c.body.some((n) => n instanceof TrackDecl));
730
+ const hasTracked = irRoot.components.some((c) => bodyNeedsRuntimeCells(c.body));
583
731
  const extraNames = hasTracked ? ['get', 'set', 'track'] : [];
584
732
  // Values exported by a `.vsk` module (`export const MAX = 10`) are hoisted
585
733
  // into this file's `__vesk` and must be destructured into component scope.
@@ -0,0 +1,16 @@
1
+ /** True when `node` contains JSX anywhere. */
2
+ export declare function containsJsx(node: unknown, depth?: number): boolean;
3
+ declare const RAW = "__raw";
4
+ /**
5
+ * JSX text semantics, matching what the compiler emits for StaticNode text:
6
+ * lines are trimmed, blank lines vanish, and interior newlines collapse to a
7
+ * single space. Getting this wrong is visible as stray whitespace in SSR HTML.
8
+ */
9
+ export declare function normalizeJsxText(value: string): string;
10
+ /**
11
+ * Rewrite every JSX node inside `ast` into the string it would have rendered.
12
+ * Returns the original node when there is no JSX, so the common path is free.
13
+ */
14
+ export declare function lowerJsxInExpression(ast: unknown): unknown;
15
+ export { RAW as RAW_HELPER };
16
+ //# sourceMappingURL=server-jsx-lowering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-jsx-lowering.d.ts","sourceRoot":"","sources":["../src/server-jsx-lowering.ts"],"names":[],"mappings":"AAwBA,8CAA8C;AAC9C,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAa7D;AAKD,QAAA,MAAM,GAAG,UAAU,CAAC;AAEpB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBtD;AAiLD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAG1D;AAED,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,CAAC"}
@@ -0,0 +1,226 @@
1
+ /** True when `node` contains JSX anywhere. */
2
+ export function containsJsx(node, depth = 0) {
3
+ if (!node || typeof node !== 'object' || depth > 40)
4
+ return false;
5
+ if (Array.isArray(node)) {
6
+ for (const item of node)
7
+ if (containsJsx(item, depth + 1))
8
+ return true;
9
+ return false;
10
+ }
11
+ const type = node.type;
12
+ if (type === 'JSXElement' || type === 'JSXFragment')
13
+ return true;
14
+ for (const key of Object.keys(node)) {
15
+ const value = node[key];
16
+ if (value && typeof value === 'object' && containsJsx(value, depth + 1))
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+ /** The helper names the generated server body already defines. */
22
+ const ESCAPE = '__escape';
23
+ const ATTR = '__attr';
24
+ const RAW = '__raw';
25
+ /**
26
+ * JSX text semantics, matching what the compiler emits for StaticNode text:
27
+ * lines are trimmed, blank lines vanish, and interior newlines collapse to a
28
+ * single space. Getting this wrong is visible as stray whitespace in SSR HTML.
29
+ */
30
+ export function normalizeJsxText(value) {
31
+ const lines = value.split(/\r\n|\n|\r/);
32
+ let lastNonEmpty = -1;
33
+ for (let i = 0; i < lines.length; i++) {
34
+ if (/[^ \t]/.test(lines[i]))
35
+ lastNonEmpty = i;
36
+ }
37
+ let out = '';
38
+ for (let i = 0; i < lines.length; i++) {
39
+ let line = lines[i].replace(/\t/g, ' ');
40
+ if (i !== 0)
41
+ line = line.replace(/^ +/, '');
42
+ if (i !== lines.length - 1)
43
+ line = line.replace(/ +$/, '');
44
+ if (line) {
45
+ if (i !== lastNonEmpty)
46
+ line += ' ';
47
+ out += line;
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ function lower(node, ctx) {
53
+ if (Array.isArray(node))
54
+ return node.map((n) => lower(n, ctx));
55
+ if (!node || typeof node !== 'object')
56
+ return node;
57
+ const n = node;
58
+ if (n.type === 'JSXElement') {
59
+ const opening = n.openingElement;
60
+ const name = jsxTagName(opening.name);
61
+ const attrs = lowerAttributes(opening.attributes, ctx);
62
+ const children = lowerChildren(n.children, ctx);
63
+ // statics[0] opens the tag, attributes interleave, then '>', then children,
64
+ // then the closing tag. Getting the '>' placement wrong silently produced
65
+ // `<lia</li>` — a tag that never closes and swallows the next element.
66
+ const statics = [`<${name}`, ...attrs.statics.slice(1)];
67
+ statics[statics.length - 1] += '>';
68
+ const quasis = [...statics, `</${name}>`];
69
+ const expressions = [...attrs.exprs, ...children];
70
+ return template(quasis, expressions);
71
+ }
72
+ if (n.type === 'JSXFragment') {
73
+ return template(['', ''], lowerChildren(n.children, ctx));
74
+ }
75
+ // Any other node: rebuild it with lowered children.
76
+ const out = {};
77
+ for (const key of Object.keys(n)) {
78
+ if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'range') {
79
+ out[key] = n[key];
80
+ continue;
81
+ }
82
+ const value = n[key];
83
+ out[key] = value && typeof value === 'object' ? lower(value, ctx) : value;
84
+ }
85
+ return out;
86
+ }
87
+ function jsxTagName(nameNode) {
88
+ const n = nameNode;
89
+ if (!n)
90
+ return 'div';
91
+ if (n.type === 'JSXIdentifier')
92
+ return String(n.name);
93
+ if (n.type === 'JSXMemberExpression') {
94
+ const object = jsxTagName(n.object);
95
+ const property = n.property;
96
+ return `${object}.${String(property?.name)}`;
97
+ }
98
+ if (n.type === 'JSXNamespacedName') {
99
+ const ns = n.namespace;
100
+ const name = n.name;
101
+ return `${String(ns?.name)}:${String(name?.name)}`;
102
+ }
103
+ return 'div';
104
+ }
105
+ function lowerAttributes(attrs, ctx) {
106
+ const statics = [''];
107
+ const exprs = [];
108
+ const push = (text, expr) => {
109
+ if (expr === undefined) {
110
+ statics[statics.length - 1] += text;
111
+ return;
112
+ }
113
+ statics[statics.length - 1] += text;
114
+ statics.push('');
115
+ exprs.push(expr);
116
+ };
117
+ for (const attr of attrs || []) {
118
+ const a = attr;
119
+ if (a.type === 'JSXSpreadAttribute') {
120
+ // A spread inside a function-valued child is not lowered; dropping it
121
+ // would be silently wrong, so it is reported instead.
122
+ console.error('[vesk] a spread attribute in a JSX expression is not supported yet (dropped):', String(a.argument ?? '').slice(0, 60));
123
+ continue;
124
+ }
125
+ const name = jsxAttrName(a.name);
126
+ const value = a.value;
127
+ if (value === null || value === undefined) {
128
+ push(` ${name}`);
129
+ continue;
130
+ }
131
+ if (value.type === 'StringLiteral') {
132
+ push(` ${name}="${escapeAttrText(String(value.value))}"`);
133
+ continue;
134
+ }
135
+ if (value.type === 'JSXExpressionContainer') {
136
+ const inner = value.expression;
137
+ if (!inner || inner.type === 'JSXEmptyExpression') {
138
+ push(` ${name}="true"`);
139
+ continue;
140
+ }
141
+ push(` ${name}="`, callNode(ATTR, [literal(name), coerceString(lower(inner, ctx))]));
142
+ push('"');
143
+ continue;
144
+ }
145
+ push(` ${name}="`, callNode(ATTR, [literal(name), coerceString(lower(value, ctx))]));
146
+ push('"');
147
+ }
148
+ return { statics, exprs };
149
+ }
150
+ function jsxAttrName(nameNode) {
151
+ const n = nameNode;
152
+ if (!n)
153
+ return 'data';
154
+ if (n.type === 'JSXIdentifier')
155
+ return String(n.name);
156
+ return jsxTagName(n);
157
+ }
158
+ function lowerChildren(children, ctx) {
159
+ const out = [];
160
+ for (const child of children || []) {
161
+ const c = child;
162
+ if (c.type === 'JSXText') {
163
+ const text = normalizeJsxText(String(c.value ?? ''));
164
+ if (text)
165
+ out.push(literal(text));
166
+ continue;
167
+ }
168
+ if (c.type === 'JSXExpressionContainer') {
169
+ const inner = c.expression;
170
+ if (!inner || inner.type === 'JSXEmptyExpression')
171
+ continue;
172
+ const lowered = lower(inner, ctx);
173
+ out.push(coerceString(lowered));
174
+ continue;
175
+ }
176
+ const lowered = lower(c, ctx);
177
+ out.push(coerceString(lowered));
178
+ }
179
+ return out;
180
+ }
181
+ /** `{v}` in HTML output must be escaped, exactly as the body emitter does. */
182
+ function coerceString(node) {
183
+ if (!node || typeof node !== 'object')
184
+ return callNode(ESCAPE, [literal(String(node))]);
185
+ const n = node;
186
+ // Already a template literal we built: its parts are static text, but any
187
+ // expression inside came from a nested container that escaped itself.
188
+ if (n.type === 'TemplateLiteral')
189
+ return n;
190
+ if (n.type === 'Literal' || n.type === 'StringLiteral')
191
+ return callNode(ESCAPE, [node]);
192
+ return callNode(ESCAPE, [callNode('String', [node])]);
193
+ }
194
+ /** Assemble a template literal: `statics.length === expressions.length + 1`. */
195
+ function template(statics, expressions) {
196
+ const quasis = statics.map((raw, i) => ({
197
+ type: 'TemplateElement',
198
+ value: { raw },
199
+ tail: i === statics.length - 1,
200
+ }));
201
+ return { type: 'TemplateLiteral', quasis, expressions };
202
+ }
203
+ function literal(value) {
204
+ return { type: 'Literal', value };
205
+ }
206
+ function callNode(callee, args) {
207
+ return {
208
+ type: 'CallExpression',
209
+ callee: { type: 'Identifier', name: callee },
210
+ arguments: args,
211
+ optional: false,
212
+ };
213
+ }
214
+ function escapeAttrText(value) {
215
+ return value.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;');
216
+ }
217
+ /**
218
+ * Rewrite every JSX node inside `ast` into the string it would have rendered.
219
+ * Returns the original node when there is no JSX, so the common path is free.
220
+ */
221
+ export function lowerJsxInExpression(ast) {
222
+ if (!containsJsx(ast))
223
+ return ast;
224
+ return lower(ast, { track: false });
225
+ }
226
+ export { RAW as RAW_HELPER };
@@ -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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"}
@@ -5,7 +5,7 @@ import { parse } from '@vesk/compiler/src/parser';
5
5
  import { generateIR } from '@vesk/compiler/src/ir-generator';
6
6
  import { compileClient, isStaticIR } from '@vesk/compiler/src/client-codegen';
7
7
  import { isStatic, prettifyHtml, resetVskState, loadRuntimeImports, evalTopLevelCode, callStaticProps, callLoadFunction, safeJsonForScript, quoteAttr, } from '@vesk/compiler/src/server-utils';
8
- import { renderHeadHtml, mergeHeadHtml } from '@vesk/compiler/src/server-head';
8
+ import { renderHeadHtml, renderStaticMetadataHtml, mergeHeadHtml } from '@vesk/compiler/src/server-head';
9
9
  import { buildComponentMap } from '@vesk/compiler/src/server-jsgen';
10
10
  import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
11
11
  import { collectVskImportPaths, collectVskReexportPaths, vskRegistryAliases, applyVskRegistryAliases } from '@vesk/compiler/src/vsk-imports';
@@ -26,6 +26,7 @@ export function compileFile(source, options) {
26
26
  const owners = new VskComponentOwners();
27
27
  const result = compileFileInternal(source, options?.sourcePath, new Set(), owners);
28
28
  owners.report('SSR');
29
+ result.metadataSource = result.ir.metadataSource || null;
29
30
  return result;
30
31
  }
31
32
  function compileFileInternal(source, sourcePath, seenImportFiles, owners) {
@@ -159,6 +160,8 @@ export function renderPage(source, componentName, props = {}, registry = new Map
159
160
  throw new Error(`Component "${componentName}" not found in source`);
160
161
  const fullRegistry = new Map([...registry, ...componentMap]);
161
162
  const targetComp = ir.components.find((c) => c.name === componentName);
163
+ // A static `export const metadata` on this .vsk file, if any.
164
+ const metadataSource = (ir.metadataSource || null);
162
165
  const doRender = (ssrProps) => {
163
166
  globalThis.__vsk_ssr = true;
164
167
  // Reuse an in-flight render token: the generated adapter handler renders
@@ -187,9 +190,12 @@ export function renderPage(source, componentName, props = {}, registry = new Map
187
190
  }
188
191
  return {
189
192
  body: bodyHtml,
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)),
193
+ // A static `export const metadata` wins: it is evaluated in a sealed
194
+ // sandbox, so it cannot depend on the component's frame resolving
195
+ // (the failure mode that shipped an empty <title>). Whatever the
196
+ // page's `<Head>` adds is appended, not lost.
197
+ head: [renderStaticMetadataHtml(metadataSource), renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk))]
198
+ .filter(Boolean).join('\n'),
193
199
  props: ssrProps
194
200
  };
195
201
  })();
@@ -201,7 +207,10 @@ export function renderPage(source, componentName, props = {}, registry = new Map
201
207
  finally {
202
208
  delete globalThis.__vsk_ssr;
203
209
  }
204
- const headHtml = targetComp ? renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk)) : '';
210
+ const headHtml = [
211
+ renderStaticMetadataHtml(metadataSource),
212
+ targetComp ? renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk)) : '',
213
+ ].filter(Boolean).join('\n');
205
214
  const pending = globalThis[`__vsk_ssr_promises_${renderToken}`];
206
215
  if (pending && pending.length > 0) {
207
216
  // A sync component still started server resources (useFetch): settle them
@@ -236,6 +245,7 @@ function clearSsrCells(token) {
236
245
  if (!token)
237
246
  return;
238
247
  delete globalThis[`__vsk_ssr_promises_${token}`];
248
+ delete globalThis[`__vsk_ssr_deferred_${token}`];
239
249
  delete globalThis[`__vsk_ssr_failures_${token}`];
240
250
  // This render is done with the token: release the slot so the reaper below
241
251
  // can collect paths that never run renderFullPage (a bare renderPage/render
@@ -516,8 +526,11 @@ export async function renderFullPage(source, componentName, props = {}, registry
516
526
  const bodyHtml = prettifyHtml(rendered.body);
517
527
  const cssUrls = options.cssUrls || (options.cssUrl ? [options.cssUrl] : []);
518
528
  const cssLink = cssUrls.map(u => `\t<link rel="stylesheet" href="${u}" />\n`).join('');
529
+ // `crossorigin` is required for SRI to be enforced on a module script; a
530
+ // same-origin module is fetched in CORS mode, so the attribute is harmless
531
+ // and makes the digest meaningful.
519
532
  const clientScript = options.clientScriptUrl
520
- ? `\t<script type="module" src="${options.clientScriptUrl}"></script>\n`
533
+ ? `\t<script type="module" src="${options.clientScriptUrl}"${options.clientScriptIntegrity ? ` integrity="${options.clientScriptIntegrity}" crossorigin="anonymous"` : ''}></script>\n`
521
534
  : '';
522
535
  const dataScripts = buildDataScripts(ssrProps, ssrData, options.externalDataScript);
523
536
  const dataScriptBlock = dataScripts.length > 0 ? '\n' + dataScripts.join('\n') + '\n' : '';
@@ -602,6 +615,7 @@ export function renderPageStream(source, componentName, props = {}, registry = n
602
615
  throw new Error(`Component "${componentName}" not found in source`);
603
616
  resetVskState(!!options.hydrate, options.markerless !== false);
604
617
  const targetComp = ir.components.find((c) => c.name === componentName);
618
+ const metadataSource = (cached?.metadataSource ?? ir.metadataSource ?? null);
605
619
  const cssUrls = options.cssUrls || (options.cssUrl ? [options.cssUrl] : []);
606
620
  const cssLink = cssUrls.map(u => `\t<link rel="stylesheet" href="${u}" />\n`).join('');
607
621
  const headParts = [];
@@ -618,7 +632,10 @@ export function renderPageStream(source, componentName, props = {}, registry = n
618
632
  }
619
633
  let streamHeadHtml = null;
620
634
  if (targetComp) {
621
- let headHtml = renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk));
635
+ let headHtml = [
636
+ renderStaticMetadataHtml(metadataSource),
637
+ renderHeadHtml(targetComp, ssrProps, scopedVesk(renderFn, __vesk)),
638
+ ].filter(Boolean).join('\n');
622
639
  if (options.pageHead) {
623
640
  const merged = mergeHeadHtml(options.pageHead, headHtml);
624
641
  headHtml = merged.html;
@@ -675,7 +692,7 @@ export function renderPageStream(source, componentName, props = {}, registry = n
675
692
  // Mirror renderFullPage: a full document must carry the client bundle or
676
693
  // the page can never hydrate (dev-server streams hit this path).
677
694
  const clientScript = options.clientScriptUrl
678
- ? `\t<script type="module" src="${options.clientScriptUrl}"></script>\n`
695
+ ? `\t<script type="module" src="${options.clientScriptUrl}"${options.clientScriptIntegrity ? ` integrity="${options.clientScriptIntegrity}" crossorigin="anonymous"` : ''}></script>\n`
679
696
  : '';
680
697
  yield `\n</div>${dataScriptBlock}${clientScript}</body>\n</html>\n`;
681
698
  }
package/dist/types.d.ts CHANGED
@@ -4,11 +4,23 @@ export interface CompileFileResult {
4
4
  ir: IRRoot;
5
5
  componentMap: Map<string, Function>;
6
6
  __vesk: Record<string, unknown>;
7
+ /**
8
+ * Source of a static `export const metadata`, hoisted from the IR so a
9
+ * precompiled (source-less) render can still serialize the head.
10
+ */
11
+ metadataSource?: string | null;
7
12
  }
8
13
  export interface FullPageOptions {
9
14
  cssUrl?: string;
10
15
  cssUrls?: string[];
11
16
  clientScriptUrl?: string;
17
+ /**
18
+ * SRI digest for `clientScriptUrl` (`sha384-<base64>`). A production build
19
+ * passes the digest of the exact bundle it emitted, so the tag cannot execute
20
+ * anything but the built bytes. Omitted in dev, where the URL is rewritten on
21
+ * every edit and a digest would block it.
22
+ */
23
+ clientScriptIntegrity?: string;
12
24
  pageHead?: string;
13
25
  security?: import('@vesk/types').VeskSecurity;
14
26
  __vesk?: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIpD,YAAY,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,OAAO,EACP,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,EACP,QAAQ,EACR,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,aAAa,EAAE,YAAY,CAAC;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,aAAa,EAAE,kBAAkB,CAAC;IAC9D,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;IAC7C;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,shBAahB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIpD,YAAY,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,OAAO,EACP,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,EACP,QAAQ,EACR,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,aAAa,EAAE,YAAY,CAAC;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,aAAa,EAAE,kBAAkB,CAAC;IAC9D,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;IAC7C;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,shBAahB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.50-canary.1",
3
+ "version": "0.2.50-canary.11",
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.50-canary.1",
23
+ "@vesk/types": "^0.2.50-canary.11",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",