@barefootjs/jsx 0.17.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -352,6 +352,7 @@ function tsNodeToParsedExpr(node) {
352
352
  }
353
353
  var CALLBACK_METHODS = new Set([
354
354
  "filter",
355
+ "map",
355
356
  "every",
356
357
  "some",
357
358
  "find",
@@ -1813,6 +1814,57 @@ function stringifyParsedExpr(expr) {
1813
1814
  return expr.raw;
1814
1815
  }
1815
1816
  }
1817
+ function materializeGetterCalls(expr, names) {
1818
+ const rw = (e) => materializeGetterCalls(e, names);
1819
+ switch (expr.kind) {
1820
+ case "call":
1821
+ if (expr.args.length === 0 && expr.callee.kind === "identifier" && names.has(expr.callee.name)) {
1822
+ return { kind: "identifier", name: expr.callee.name };
1823
+ }
1824
+ return { kind: "call", callee: rw(expr.callee), args: expr.args.map(rw) };
1825
+ case "binary":
1826
+ return { kind: "binary", op: expr.op, left: rw(expr.left), right: rw(expr.right) };
1827
+ case "logical":
1828
+ return { kind: "logical", op: expr.op, left: rw(expr.left), right: rw(expr.right) };
1829
+ case "unary":
1830
+ return { kind: "unary", op: expr.op, argument: rw(expr.argument) };
1831
+ case "conditional":
1832
+ return {
1833
+ kind: "conditional",
1834
+ test: rw(expr.test),
1835
+ consequent: rw(expr.consequent),
1836
+ alternate: rw(expr.alternate)
1837
+ };
1838
+ case "member":
1839
+ return { kind: "member", object: rw(expr.object), property: expr.property, computed: expr.computed };
1840
+ case "index-access":
1841
+ return { kind: "index-access", object: rw(expr.object), index: rw(expr.index) };
1842
+ case "template-literal":
1843
+ return {
1844
+ kind: "template-literal",
1845
+ parts: expr.parts.map((p) => p.type === "string" ? p : { type: "expression", expr: rw(p.expr) })
1846
+ };
1847
+ case "array-literal":
1848
+ return { kind: "array-literal", elements: expr.elements.map(rw) };
1849
+ case "array-method":
1850
+ if (expr.method === "flat")
1851
+ return { ...expr, object: rw(expr.object) };
1852
+ return { ...expr, object: rw(expr.object), args: expr.args.map(rw) };
1853
+ case "object-literal":
1854
+ return {
1855
+ kind: "object-literal",
1856
+ raw: expr.raw,
1857
+ properties: expr.properties.map((p) => ({ ...p, value: rw(p.value) }))
1858
+ };
1859
+ case "arrow":
1860
+ return { kind: "arrow", params: expr.params, body: rw(expr.body) };
1861
+ case "identifier":
1862
+ case "literal":
1863
+ case "regex":
1864
+ case "unsupported":
1865
+ return expr;
1866
+ }
1867
+ }
1816
1868
  function serializeParsedExpr(expr) {
1817
1869
  const node = toEvalNode(expr);
1818
1870
  return node === null ? null : JSON.stringify(node);
@@ -1862,8 +1914,13 @@ function freeVarsInBody(body, params) {
1862
1914
  for (const p of e.properties)
1863
1915
  visit(p.value);
1864
1916
  return;
1865
- case "literal":
1866
1917
  case "array-method":
1918
+ if (e.method === "includes") {
1919
+ visit(e.object);
1920
+ e.args.forEach(visit);
1921
+ }
1922
+ return;
1923
+ case "literal":
1867
1924
  case "arrow":
1868
1925
  case "regex":
1869
1926
  case "unsupported":
@@ -1873,6 +1930,72 @@ function freeVarsInBody(body, params) {
1873
1930
  visit(body);
1874
1931
  return [...found].sort();
1875
1932
  }
1933
+ function freeIdentifiers(expr) {
1934
+ const free = new Set;
1935
+ function visit(e, bound) {
1936
+ switch (e.kind) {
1937
+ case "literal":
1938
+ case "regex":
1939
+ return true;
1940
+ case "identifier":
1941
+ if (!bound.has(e.name))
1942
+ free.add(e.name);
1943
+ return true;
1944
+ case "call": {
1945
+ const isBuiltinCallee = evalBuiltinCalleeName(e.callee) !== null;
1946
+ if (!isBuiltinCallee && !visit(e.callee, bound))
1947
+ return false;
1948
+ for (const a of e.args)
1949
+ if (!visit(a, bound))
1950
+ return false;
1951
+ return true;
1952
+ }
1953
+ case "member":
1954
+ return visit(e.object, bound);
1955
+ case "index-access":
1956
+ return visit(e.object, bound) && visit(e.index, bound);
1957
+ case "binary":
1958
+ case "logical":
1959
+ return visit(e.left, bound) && visit(e.right, bound);
1960
+ case "unary":
1961
+ return visit(e.argument, bound);
1962
+ case "conditional":
1963
+ return visit(e.test, bound) && visit(e.consequent, bound) && visit(e.alternate, bound);
1964
+ case "template-literal":
1965
+ for (const p of e.parts) {
1966
+ if (p.type === "expression" && !visit(p.expr, bound))
1967
+ return false;
1968
+ }
1969
+ return true;
1970
+ case "array-literal":
1971
+ for (const el of e.elements)
1972
+ if (!visit(el, bound))
1973
+ return false;
1974
+ return true;
1975
+ case "array-method":
1976
+ if (!visit(e.object, bound))
1977
+ return false;
1978
+ for (const a of e.args)
1979
+ if (!visit(a, bound))
1980
+ return false;
1981
+ return true;
1982
+ case "object-literal":
1983
+ for (const p of e.properties)
1984
+ if (!visit(p.value, bound))
1985
+ return false;
1986
+ return true;
1987
+ case "arrow": {
1988
+ const inner = new Set(bound);
1989
+ for (const p of e.params)
1990
+ inner.add(p);
1991
+ return visit(e.body, inner);
1992
+ }
1993
+ case "unsupported":
1994
+ return false;
1995
+ }
1996
+ }
1997
+ return visit(expr, new Set) ? free : null;
1998
+ }
1876
1999
  var EVAL_BINARY_OPS = new Set([
1877
2000
  "+",
1878
2001
  "-",
@@ -1998,7 +2121,14 @@ function toEvalNode(e) {
1998
2121
  }
1999
2122
  return { kind: "object-literal", properties };
2000
2123
  }
2001
- case "array-method":
2124
+ case "array-method": {
2125
+ if (e.method === "includes" && e.args.length === 1) {
2126
+ const object = toEvalNode(e.object);
2127
+ const arg = toEvalNode(e.args[0]);
2128
+ return object && arg ? { kind: "array-method", method: "includes", object, args: [arg] } : null;
2129
+ }
2130
+ return null;
2131
+ }
2002
2132
  case "arrow":
2003
2133
  case "regex":
2004
2134
  case "unsupported":
@@ -3582,8 +3712,8 @@ function propResolvesUnsafe(prop, env, unsafeLocalNames) {
3582
3712
  }
3583
3713
  if (!source)
3584
3714
  return false;
3585
- const { freeIdentifiers } = csrSubstitute(source, env);
3586
- return setIntersects(freeIdentifiers, unsafeLocalNames);
3715
+ const { freeIdentifiers: freeIdentifiers2 } = csrSubstitute(source, env);
3716
+ return setIntersects(freeIdentifiers2, unsafeLocalNames);
3587
3717
  }
3588
3718
  function computeDeferredChildSlots(node, ctx, inlinableConstants, unsafeLocalNames, propsObjectName) {
3589
3719
  const deferred = new Set;
@@ -3636,8 +3766,8 @@ function generateCsrTemplateWithOpts(node, opts) {
3636
3766
  const source = templateExpr ?? expr;
3637
3767
  if (!source)
3638
3768
  return source;
3639
- const { rewritten, freeIdentifiers } = csrSubstitute(source, env);
3640
- if (unsafeLocalNames && unsafeLocalNames.size > 0 && setIntersects(freeIdentifiers, unsafeLocalNames)) {
3769
+ const { rewritten, freeIdentifiers: freeIdentifiers2 } = csrSubstitute(source, env);
3770
+ if (unsafeLocalNames && unsafeLocalNames.size > 0 && setIntersects(freeIdentifiers2, unsafeLocalNames)) {
3641
3771
  return UNSAFE_TEMPLATE_EXPR;
3642
3772
  }
3643
3773
  return applyPropsRewrite(rewritten, propsObjectName ?? null);
@@ -6301,7 +6431,7 @@ function collectConstant(node, ctx, _isModule, declarationKind = "const", isExpo
6301
6431
  const baseValue = `${propsName}.${sourceKey}`;
6302
6432
  const value2 = defaultValueExpr ? `${baseValue} ?? ${defaultValueExpr}` : baseValue;
6303
6433
  const containsArrow2 = el.initializer ? nodeContainsArrow(el.initializer) : false;
6304
- const freeIdentifiers2 = el.initializer ? extractFreeIdentifiersFromNode(el.initializer) : new Set([propsName]);
6434
+ const freeIdentifiers3 = el.initializer ? extractFreeIdentifiersFromNode(el.initializer) : new Set([propsName]);
6305
6435
  ctx.localConstants.push({
6306
6436
  name: localName,
6307
6437
  value: value2,
@@ -6309,7 +6439,7 @@ function collectConstant(node, ctx, _isModule, declarationKind = "const", isExpo
6309
6439
  isExported,
6310
6440
  type: null,
6311
6441
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath),
6312
- freeIdentifiers: freeIdentifiers2,
6442
+ freeIdentifiers: freeIdentifiers3,
6313
6443
  containsArrow: containsArrow2 || undefined,
6314
6444
  origin: { phase: "hydrate", scope: "init", effect: "pure" }
6315
6445
  });
@@ -6393,7 +6523,7 @@ function collectConstant(node, ctx, _isModule, declarationKind = "const", isExpo
6393
6523
  } else if (value) {
6394
6524
  type = inferTypeFromValue(value);
6395
6525
  }
6396
- const freeIdentifiers = node.initializer ? extractFreeIdentifiersFromNode(node.initializer) : undefined;
6526
+ const freeIdentifiers2 = node.initializer ? extractFreeIdentifiersFromNode(node.initializer) : undefined;
6397
6527
  const containsArrow = node.initializer ? nodeContainsArrow(node.initializer) : false;
6398
6528
  const systemConstructKind = node.initializer ? getSystemConstructKind(node.initializer) : undefined;
6399
6529
  let templateValue;
@@ -6442,7 +6572,7 @@ function collectConstant(node, ctx, _isModule, declarationKind = "const", isExpo
6442
6572
  isModule: isModule || undefined,
6443
6573
  type,
6444
6574
  loc: getSourceLocation(node, ctx.sourceFile, ctx.filePath),
6445
- freeIdentifiers,
6575
+ freeIdentifiers: freeIdentifiers2,
6446
6576
  isJsx,
6447
6577
  isJsxFunction: isJsxFunction || undefined,
6448
6578
  containsArrow: containsArrow || undefined,
@@ -9843,14 +9973,14 @@ function processAttributes(attributes, ctx) {
9843
9973
  clientOnly = true;
9844
9974
  }
9845
9975
  }
9846
- const freeIdentifiers = attrFreeIdentifiers(attr);
9976
+ const freeIdentifiers2 = attrFreeIdentifiers(attr);
9847
9977
  attrs.push({
9848
9978
  name,
9849
9979
  value,
9850
9980
  clientOnly,
9851
9981
  loc: getSourceLocation(attr, ctx.sourceFile, ctx.filePath),
9852
9982
  ...computeReactivityFlags(attr, ctx),
9853
- ...freeIdentifiers !== undefined && { freeIdentifiers }
9983
+ ...freeIdentifiers2 !== undefined && { freeIdentifiers: freeIdentifiers2 }
9854
9984
  });
9855
9985
  }
9856
9986
  return { attrs, events, ref };
@@ -10173,14 +10303,14 @@ function processComponentProps(attributes, ctx) {
10173
10303
  clientOnly = true;
10174
10304
  }
10175
10305
  }
10176
- const freeIdentifiers = attrFreeIdentifiers(attr);
10306
+ const freeIdentifiers2 = attrFreeIdentifiers(attr);
10177
10307
  props.push({
10178
10308
  name,
10179
10309
  value,
10180
10310
  clientOnly,
10181
10311
  loc: getSourceLocation(attr, ctx.sourceFile, ctx.filePath),
10182
10312
  ...computeReactivityFlags(attr, ctx),
10183
- ...freeIdentifiers !== undefined && { freeIdentifiers }
10313
+ ...freeIdentifiers2 !== undefined && { freeIdentifiers: freeIdentifiers2 }
10184
10314
  });
10185
10315
  }
10186
10316
  return props;
@@ -10596,7 +10726,7 @@ function decideWrapForChildProp(expandedValue, ctx, prop) {
10596
10726
  return { wrap: true, reason: "props-access" };
10597
10727
  return decideWrapForAttr(expandedValue, ctx, prop);
10598
10728
  }
10599
- function needsEffectWrapper(expr, ctx, freeIdentifiers) {
10729
+ function needsEffectWrapper(expr, ctx, freeIdentifiers2) {
10600
10730
  for (const signal of ctx.signals) {
10601
10731
  if (new RegExp(`\\b${signal.getter}\\s*\\(`).test(expr)) {
10602
10732
  return true;
@@ -10610,7 +10740,7 @@ function needsEffectWrapper(expr, ctx, freeIdentifiers) {
10610
10740
  for (const prop of ctx.propsParams) {
10611
10741
  if (prop.name === "children")
10612
10742
  continue;
10613
- if (freeIdentifiers ? freeIdentifiers.has(prop.name) : tokenContainsIdent(expr, prop.name)) {
10743
+ if (freeIdentifiers2 ? freeIdentifiers2.has(prop.name) : tokenContainsIdent(expr, prop.name)) {
10614
10744
  return true;
10615
10745
  }
10616
10746
  }
@@ -10621,8 +10751,8 @@ function needsEffectWrapper(expr, ctx, freeIdentifiers) {
10621
10751
  }
10622
10752
  return false;
10623
10753
  }
10624
- function classifyReactivity(expr, ctx, loopParam, loopParamBindings, freeIdentifiers) {
10625
- const has = (name) => freeIdentifiers ? freeIdentifiers.has(name) : tokenContainsIdent(expr, name);
10754
+ function classifyReactivity(expr, ctx, loopParam, loopParamBindings, freeIdentifiers2) {
10755
+ const has = (name) => freeIdentifiers2 ? freeIdentifiers2.has(name) : tokenContainsIdent(expr, name);
10626
10756
  if (loopParamBindings && loopParamBindings.length > 0) {
10627
10757
  for (const b of loopParamBindings) {
10628
10758
  if (has(b.name)) {
@@ -10632,7 +10762,7 @@ function classifyReactivity(expr, ctx, loopParam, loopParamBindings, freeIdentif
10632
10762
  } else if (loopParam && has(loopParam)) {
10633
10763
  return { kind: "loop-param", param: loopParam };
10634
10764
  }
10635
- if (needsEffectWrapper(expr, ctx, freeIdentifiers)) {
10765
+ if (needsEffectWrapper(expr, ctx, freeIdentifiers2)) {
10636
10766
  return { kind: "signal-or-memo-or-prop" };
10637
10767
  }
10638
10768
  return { kind: "none" };
@@ -12634,9 +12764,9 @@ function populateCsrInlinable(ctx, relocateEnv) {
12634
12764
  continue;
12635
12765
  const env = buildEnvWithConsts();
12636
12766
  const source = c.value.trim();
12637
- const { rewritten, freeIdentifiers } = csrSubstitute(source, env);
12767
+ const { rewritten, freeIdentifiers: freeIdentifiers2 } = csrSubstitute(source, env);
12638
12768
  let pendingDependency = false;
12639
- for (const id of freeIdentifiers) {
12769
+ for (const id of freeIdentifiers2) {
12640
12770
  if (id === c.name)
12641
12771
  continue;
12642
12772
  const dep = ctx.localConstants.find((o) => o.name === id);
@@ -12652,7 +12782,7 @@ function populateCsrInlinable(ctx, relocateEnv) {
12652
12782
  ctx.csrInlinable.set(c.name, null);
12653
12783
  } else {
12654
12784
  const bridgedRewritten = inlineResult.rewrittenValue;
12655
- const bridgedFreeIdentifiers = recomputeFreeIdentifiers(bridgedRewritten, freeIdentifiers);
12785
+ const bridgedFreeIdentifiers = recomputeFreeIdentifiers(bridgedRewritten, freeIdentifiers2);
12656
12786
  ctx.csrInlinable.set(c.name, { rewrittenValue: bridgedRewritten, freeIdentifiers: bridgedFreeIdentifiers });
12657
12787
  constSubs.set(c.name, {
12658
12788
  kind: "identifier",
@@ -13199,14 +13329,26 @@ function sortDeclarations(declarations, declNameSet, graph) {
13199
13329
  var ENV_SIGNAL_CLIENT_FACTORY = {
13200
13330
  search: "createSearchParams"
13201
13331
  };
13202
- function searchParamsLocalNames(metadata) {
13332
+ var ENV_SIGNAL_READERS = new Map([
13333
+ ["search", { key: "search", canonicalName: "searchParams", methods: new Set(["get"]) }]
13334
+ ]);
13335
+ function envSignalReaderFor(key) {
13336
+ if (key === undefined)
13337
+ return null;
13338
+ return ENV_SIGNAL_READERS.get(key) ?? null;
13339
+ }
13340
+ function envSignalLocalNames(metadata, key) {
13203
13341
  const names = new Set;
13204
13342
  for (const s of metadata.signals) {
13205
- if (s.envReader === "search")
13343
+ if (s.envReader !== undefined && (key === undefined || s.envReader === key)) {
13206
13344
  names.add(s.getter);
13345
+ }
13207
13346
  }
13208
13347
  return names;
13209
13348
  }
13349
+ function searchParamsLocalNames(metadata) {
13350
+ return envSignalLocalNames(metadata, "search");
13351
+ }
13210
13352
  function importsSearchParams(metadata) {
13211
13353
  return searchParamsLocalNames(metadata).size > 0;
13212
13354
  }
@@ -17897,123 +18039,503 @@ function evalNode(node, ctx) {
17897
18039
  return UNRESOLVED;
17898
18040
  }
17899
18041
 
17900
- // src/compiler.ts
17901
- function mergeTemplateImports(lines) {
17902
- const result = [];
17903
- const valueIdx = new Map;
17904
- const valueNames = new Map;
17905
- const typeIdx = new Map;
17906
- const typeNames = new Map;
17907
- const seenOther = new Set;
17908
- const fold = (src, rawNames, idx, names, render) => {
17909
- if (!idx.has(src)) {
17910
- idx.set(src, result.length);
17911
- names.set(src, new Set);
17912
- result.push("");
17913
- }
17914
- const set = names.get(src);
17915
- for (const n of rawNames.split(",").map((s) => s.trim()).filter(Boolean))
17916
- set.add(n);
17917
- result[idx.get(src)] = render(src, set);
17918
- };
17919
- for (const raw of lines) {
17920
- const line = raw.trim();
17921
- if (!line)
18042
+ // src/augment-inherited-props.ts
18043
+ import ts17 from "typescript";
18044
+ function collectContextConsumers(metadata) {
18045
+ const constants = metadata.localConstants ?? [];
18046
+ const contextDefaults = new Map;
18047
+ for (const c of constants) {
18048
+ if (c.systemConstructKind !== "createContext" || c.value === undefined)
17922
18049
  continue;
17923
- const typeMatch = line.match(/^import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/);
17924
- const valueMatch = line.match(/^import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/);
17925
- if (valueMatch) {
17926
- fold(valueMatch[2], valueMatch[1], valueIdx, valueNames, (s, n) => `import { ${[...n].join(", ")} } from '${s}'`);
17927
- } else if (typeMatch) {
17928
- fold(typeMatch[2], typeMatch[1], typeIdx, typeNames, (s, n) => `import type { ${[...n].join(", ")} } from '${s}'`);
17929
- } else if (!seenOther.has(line)) {
17930
- seenOther.add(line);
17931
- result.push(line);
17932
- }
18050
+ contextDefaults.set(c.name, parseCreateContextDefault(c.value));
17933
18051
  }
17934
- return result.filter(Boolean).join(`
17935
- `);
17936
- }
17937
- function compileMultipleComponents(source, filePath, componentNames, options) {
17938
- const files = [];
17939
- const errors = [];
17940
- const adapter = options.adapter;
17941
- const entries = [];
17942
- const program = options.program ?? (needsTypeBasedDetection(source) ? createProgramForFile(source, filePath)?.program : undefined);
17943
- for (const componentName of componentNames) {
17944
- const ctx = analyzeComponent(source, filePath, componentName, program);
17945
- if (!ctx.jsxReturn) {
17946
- errors.push(...ctx.errors);
18052
+ if (contextDefaults.size === 0)
18053
+ return [];
18054
+ const consumers = [];
18055
+ for (const c of constants) {
18056
+ if (c.value === undefined)
17947
18057
  continue;
17948
- }
17949
- const ir = jsxToIR(ctx);
17950
- errors.push(...ctx.errors);
17951
- if (!ir)
18058
+ const ctxName = parseUseContextArg(c.value);
18059
+ if (ctxName === null || !contextDefaults.has(ctxName))
17952
18060
  continue;
17953
- const componentIR = {
17954
- version: "0.1",
17955
- metadata: buildMetadata(ctx),
17956
- root: ir,
17957
- errors: []
17958
- };
17959
- componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
17960
- if (options.cssLayerPrefix) {
17961
- applyCssLayerPrefix(componentIR, options.cssLayerPrefix);
17962
- }
17963
- entries.push({ componentIR, ctx });
17964
- }
17965
- if (options.outputIR) {
17966
- for (const { componentIR } of entries) {
17967
- const componentName = componentIR.metadata.componentName;
17968
- files.push({
17969
- path: filePath.replace(/\.tsx?$/, `.${componentName}.ir.json`),
17970
- content: JSON.stringify(componentIR, null, 2),
17971
- type: "ir"
17972
- });
17973
- }
17974
- }
17975
- const allOutputs = [];
17976
- const defaultExportName = entries.find((e) => e.componentIR.metadata.hasDefaultExport)?.componentIR.metadata.componentName;
17977
- const fileWideInlineExported = new Set;
17978
- for (const { componentIR } of entries) {
17979
- for (const name of collectInlineExportedNames(componentIR)) {
17980
- fileWideInlineExported.add(name);
17981
- }
17982
- }
17983
- const moduleConstantsSet = new Set;
17984
- const moduleConstantsOrdered = [];
17985
- const fileScope = computeFileScope(filePath);
17986
- const nonExportedSiblings = new Set;
17987
- for (const { componentIR } of entries) {
17988
- if (!componentIR.metadata.isExported) {
17989
- nonExportedSiblings.add(componentIR.metadata.componentName);
17990
- }
18061
+ consumers.push({
18062
+ localName: c.name,
18063
+ contextName: ctxName,
18064
+ defaultValue: contextDefaults.get(ctxName) ?? null
18065
+ });
17991
18066
  }
17992
- setActiveComponentScope({ fileScope, nonExportedSiblings });
17993
- const multiAdapterCaps = {
17994
- templatePrimitives: options.adapter.templatePrimitives,
17995
- acceptsTemplateCall: options.adapter.acceptsTemplateCall
18067
+ return consumers;
18068
+ }
18069
+ function parseUseContextArg(source) {
18070
+ const expr = parseSingleExpression(source);
18071
+ if (!expr || !ts17.isCallExpression(expr))
18072
+ return null;
18073
+ if (!ts17.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
18074
+ return null;
18075
+ if (expr.arguments.length !== 1)
18076
+ return null;
18077
+ const arg = expr.arguments[0];
18078
+ return ts17.isIdentifier(arg) ? arg.text : null;
18079
+ }
18080
+ function parseCreateContextDefault(source) {
18081
+ const expr = parseSingleExpression(source);
18082
+ if (!expr || !ts17.isCallExpression(expr))
18083
+ return null;
18084
+ if (expr.arguments.length === 0)
18085
+ return null;
18086
+ const arg = expr.arguments[0];
18087
+ if (ts17.isStringLiteral(arg) || ts17.isNoSubstitutionTemplateLiteral(arg))
18088
+ return arg.text;
18089
+ if (ts17.isNumericLiteral(arg))
18090
+ return Number(arg.text);
18091
+ if (arg.kind === ts17.SyntaxKind.TrueKeyword)
18092
+ return true;
18093
+ if (arg.kind === ts17.SyntaxKind.FalseKeyword)
18094
+ return false;
18095
+ return null;
18096
+ }
18097
+ function parseSingleExpression(source) {
18098
+ const sf = ts17.createSourceFile("__ctx.ts", `(${source})`, ts17.ScriptTarget.Latest, false);
18099
+ const stmt = sf.statements[0];
18100
+ if (!stmt || !ts17.isExpressionStatement(stmt))
18101
+ return null;
18102
+ let e = stmt.expression;
18103
+ while (ts17.isParenthesizedExpression(e))
18104
+ e = e.expression;
18105
+ return e;
18106
+ }
18107
+ function augmentInheritedPropAccesses(ir) {
18108
+ const propsObj = ir.metadata.propsObjectName;
18109
+ if (!propsObj)
18110
+ return;
18111
+ const existing = new Set(ir.metadata.propsParams.map((p) => p.name));
18112
+ const bareRefProps = new Set;
18113
+ const booleanAttrProps = new Set;
18114
+ const accessed = new Set;
18115
+ const accessRe = new RegExp(`(?:^|[^\\w$.])${propsObj}\\.([A-Za-z_$][\\w$]*)`, "g");
18116
+ const scan = (s) => {
18117
+ if (!s)
18118
+ return;
18119
+ for (const m of s.matchAll(accessRe))
18120
+ accessed.add(m[1]);
17996
18121
  };
17997
- try {
17998
- for (const { componentIR } of entries) {
17999
- const scriptBaseName = options.scriptBaseName ?? (!componentIR.metadata.hasDefaultExport && defaultExportName ? defaultExportName : undefined);
18000
- const adapterOutput = adapter.generate(componentIR, {
18001
- scriptBaseName,
18002
- siblingTemplatesRegistered: options.siblingTemplatesRegistered,
18003
- rewriteRelativeImport: options.rewriteRelativeImport
18004
- });
18005
- const moduleExports = generateModuleExports(componentIR, fileWideInlineExported, options.rewriteRelativeImport);
18006
- const s = adapterOutput.sections;
18007
- const imports = s.imports;
18008
- const types = s.types;
18009
- const component = s.component + (s.defaultExport || "");
18010
- const mc = s.moduleConstants;
18011
- if (mc && !moduleConstantsSet.has(mc)) {
18012
- moduleConstantsSet.add(mc);
18013
- moduleConstantsOrdered.push(mc);
18014
- }
18015
- allOutputs.push({
18016
- componentName: componentIR.metadata.componentName,
18122
+ for (const memo of ir.metadata.memos)
18123
+ scan(memo.computation);
18124
+ for (const signal of ir.metadata.signals)
18125
+ scan(signal.initialValue);
18126
+ for (const stmt of ir.metadata.initStatements ?? [])
18127
+ scan(stmt.body);
18128
+ for (const eff of ir.metadata.effects ?? [])
18129
+ scan(eff.body);
18130
+ for (const c of ir.metadata.localConstants ?? []) {
18131
+ if (c.isModule)
18132
+ continue;
18133
+ scan(c.value);
18134
+ }
18135
+ const walk = (node) => {
18136
+ if (!node)
18137
+ return;
18138
+ const el = node;
18139
+ for (const attr of el.attrs ?? []) {
18140
+ const v = attr.value;
18141
+ if (v?.parts) {
18142
+ for (const part of v.parts) {
18143
+ if (part.type === "string")
18144
+ scan(part.value);
18145
+ else if (part.type === "ternary") {
18146
+ scan(part.condition);
18147
+ scan(part.whenTrue);
18148
+ scan(part.whenFalse);
18149
+ } else if (part.type === "lookup")
18150
+ scan(part.key);
18151
+ }
18152
+ }
18153
+ if (v?.kind === "expression" && typeof v.expr === "string") {
18154
+ scan(v.expr);
18155
+ const expr = v.expr.trim();
18156
+ const prefix = `${propsObj}.`;
18157
+ if (isBooleanAttr(attr.name) || v.presenceOrUndefined) {
18158
+ const m = expr.match(new RegExp(`^${propsObj}\\.([A-Za-z_$][\\w$]*)`));
18159
+ if (m)
18160
+ booleanAttrProps.add(m[1]);
18161
+ } else if (expr.startsWith(prefix)) {
18162
+ const rest = expr.slice(prefix.length);
18163
+ if (/^[A-Za-z_$][\w$]*$/.test(rest))
18164
+ bareRefProps.add(rest);
18165
+ }
18166
+ }
18167
+ }
18168
+ for (const child of el.children ?? []) {
18169
+ const c = child;
18170
+ walk(c.element ?? child);
18171
+ }
18172
+ const branchy = node;
18173
+ walk(branchy.whenTrue);
18174
+ walk(branchy.whenFalse);
18175
+ walk(branchy.consequent);
18176
+ walk(branchy.alternate);
18177
+ };
18178
+ walk(ir.root);
18179
+ for (const name of accessed) {
18180
+ if (existing.has(name))
18181
+ continue;
18182
+ let raw;
18183
+ if (booleanAttrProps.has(name))
18184
+ raw = "boolean";
18185
+ else if (bareRefProps.has(name))
18186
+ raw = "unknown";
18187
+ else
18188
+ raw = "string";
18189
+ const type = raw === "boolean" ? { kind: "primitive", raw: "boolean", primitive: "boolean" } : raw === "string" ? { kind: "primitive", raw: "string", primitive: "string" } : { kind: "unknown", raw: "unknown" };
18190
+ ir.metadata.propsParams.push({ name, type, optional: true });
18191
+ existing.add(name);
18192
+ }
18193
+ }
18194
+ function parseStaticStringConst(source) {
18195
+ const sf = ts17.createSourceFile("__const.ts", `const __x = (${source});`, ts17.ScriptTarget.Latest, false);
18196
+ const stmt = sf.statements[0];
18197
+ if (!stmt || !ts17.isVariableStatement(stmt))
18198
+ return null;
18199
+ let init = stmt.declarationList.declarations[0]?.initializer;
18200
+ while (init && ts17.isParenthesizedExpression(init))
18201
+ init = init.expression;
18202
+ if (!init)
18203
+ return null;
18204
+ if (ts17.isStringLiteral(init) || ts17.isNoSubstitutionTemplateLiteral(init)) {
18205
+ return init.text;
18206
+ }
18207
+ return evalStringArrayJoin(source);
18208
+ }
18209
+ function evalTemplateOfStringConsts(source, resolved) {
18210
+ const sf = ts17.createSourceFile("__const.ts", `const __x = (${source});`, ts17.ScriptTarget.Latest, false);
18211
+ const stmt = sf.statements[0];
18212
+ if (!stmt || !ts17.isVariableStatement(stmt))
18213
+ return null;
18214
+ let init = stmt.declarationList.declarations[0]?.initializer;
18215
+ while (init && ts17.isParenthesizedExpression(init))
18216
+ init = init.expression;
18217
+ if (!init || !ts17.isTemplateExpression(init))
18218
+ return null;
18219
+ let out = init.head.text;
18220
+ for (const span of init.templateSpans) {
18221
+ if (!ts17.isIdentifier(span.expression))
18222
+ return null;
18223
+ const value = resolved.get(span.expression.text);
18224
+ if (value === undefined)
18225
+ return null;
18226
+ out += value + span.literal.text;
18227
+ }
18228
+ return out;
18229
+ }
18230
+ function collectModuleStringConsts(constants) {
18231
+ const map = new Map;
18232
+ const candidates = (constants ?? []).filter((c) => c.isModule && c.value !== undefined);
18233
+ let progressed = true;
18234
+ while (progressed) {
18235
+ progressed = false;
18236
+ for (const c of candidates) {
18237
+ if (map.has(c.name))
18238
+ continue;
18239
+ const literal = parseStaticStringConst(c.value) ?? evalTemplateOfStringConsts(c.value, map);
18240
+ if (literal !== null) {
18241
+ map.set(c.name, literal);
18242
+ progressed = true;
18243
+ }
18244
+ }
18245
+ }
18246
+ return map;
18247
+ }
18248
+ function lookupStaticRecordLiteral(objectName, key, constants) {
18249
+ const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
18250
+ if (constInfo?.value === undefined)
18251
+ return null;
18252
+ const sf = ts17.createSourceFile("__rec.ts", `(${constInfo.value})`, ts17.ScriptTarget.Latest, true);
18253
+ if (sf.statements.length !== 1)
18254
+ return null;
18255
+ const stmt = sf.statements[0];
18256
+ if (!ts17.isExpressionStatement(stmt))
18257
+ return null;
18258
+ let parsed = stmt.expression;
18259
+ while (ts17.isParenthesizedExpression(parsed))
18260
+ parsed = parsed.expression;
18261
+ if (!ts17.isObjectLiteralExpression(parsed))
18262
+ return null;
18263
+ for (const prop of parsed.properties) {
18264
+ if (!ts17.isPropertyAssignment(prop))
18265
+ continue;
18266
+ const name = prop.name;
18267
+ const propKey = ts17.isIdentifier(name) || ts17.isStringLiteral(name) || ts17.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
18268
+ if (propKey !== key)
18269
+ continue;
18270
+ let v = prop.initializer;
18271
+ while (ts17.isParenthesizedExpression(v))
18272
+ v = v.expression;
18273
+ if (ts17.isNumericLiteral(v))
18274
+ return { kind: "number", text: v.text };
18275
+ if (ts17.isStringLiteral(v) || ts17.isNoSubstitutionTemplateLiteral(v)) {
18276
+ return { kind: "string", text: v.text };
18277
+ }
18278
+ return null;
18279
+ }
18280
+ return null;
18281
+ }
18282
+ function evalStringArrayJoin(source) {
18283
+ const sf = ts17.createSourceFile("__join.ts", `const __x = (${source});`, ts17.ScriptTarget.Latest, false);
18284
+ const stmt = sf.statements[0];
18285
+ if (!stmt || !ts17.isVariableStatement(stmt))
18286
+ return null;
18287
+ let node = stmt.declarationList.declarations[0]?.initializer;
18288
+ while (node && ts17.isParenthesizedExpression(node))
18289
+ node = node.expression;
18290
+ if (!node || !ts17.isCallExpression(node))
18291
+ return null;
18292
+ const callee = node.expression;
18293
+ if (!ts17.isPropertyAccessExpression(callee))
18294
+ return null;
18295
+ if (callee.name.text !== "join")
18296
+ return null;
18297
+ let recv = callee.expression;
18298
+ while (ts17.isParenthesizedExpression(recv))
18299
+ recv = recv.expression;
18300
+ if (!ts17.isArrayLiteralExpression(recv))
18301
+ return null;
18302
+ const parts = [];
18303
+ for (const el of recv.elements) {
18304
+ if (ts17.isStringLiteral(el) || ts17.isNoSubstitutionTemplateLiteral(el)) {
18305
+ parts.push(el.text);
18306
+ } else {
18307
+ return null;
18308
+ }
18309
+ }
18310
+ let sep2 = ",";
18311
+ if (node.arguments.length >= 1) {
18312
+ const arg = node.arguments[0];
18313
+ if (ts17.isStringLiteral(arg) || ts17.isNoSubstitutionTemplateLiteral(arg))
18314
+ sep2 = arg.text;
18315
+ else
18316
+ return null;
18317
+ }
18318
+ return parts.join(sep2);
18319
+ }
18320
+ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
18321
+ if (!ts17.isElementAccessExpression(val))
18322
+ return null;
18323
+ const obj = val.expression;
18324
+ const arg = val.argumentExpression;
18325
+ if (!ts17.isIdentifier(obj) || !ts17.isIdentifier(arg))
18326
+ return null;
18327
+ let indexPropName;
18328
+ let defaultKey;
18329
+ const resolved = resolveKey?.(arg.text);
18330
+ if (resolved) {
18331
+ indexPropName = resolved.propName;
18332
+ defaultKey = resolved.defaultLiteral;
18333
+ } else if (propsParams.some((p) => p.name === arg.text)) {
18334
+ indexPropName = arg.text;
18335
+ } else {
18336
+ return null;
18337
+ }
18338
+ const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
18339
+ if (constInfo?.value === undefined)
18340
+ return null;
18341
+ const sf = ts17.createSourceFile("__rec.ts", `(${constInfo.value})`, ts17.ScriptTarget.Latest, true);
18342
+ if (sf.statements.length !== 1)
18343
+ return null;
18344
+ const stmt = sf.statements[0];
18345
+ if (!ts17.isExpressionStatement(stmt))
18346
+ return null;
18347
+ let parsed = stmt.expression;
18348
+ while (ts17.isParenthesizedExpression(parsed))
18349
+ parsed = parsed.expression;
18350
+ if (!ts17.isObjectLiteralExpression(parsed))
18351
+ return null;
18352
+ const entries = [];
18353
+ for (const prop of parsed.properties) {
18354
+ if (!ts17.isPropertyAssignment(prop))
18355
+ return null;
18356
+ let key;
18357
+ if (ts17.isIdentifier(prop.name)) {
18358
+ key = prop.name.text;
18359
+ } else if (ts17.isStringLiteral(prop.name) || ts17.isNoSubstitutionTemplateLiteral(prop.name)) {
18360
+ key = prop.name.text;
18361
+ } else {
18362
+ return null;
18363
+ }
18364
+ let v = prop.initializer;
18365
+ while (ts17.isParenthesizedExpression(v))
18366
+ v = v.expression;
18367
+ if (ts17.isNumericLiteral(v)) {
18368
+ entries.push({ key, value: { kind: "number", text: v.text } });
18369
+ } else if (ts17.isStringLiteral(v) || ts17.isNoSubstitutionTemplateLiteral(v)) {
18370
+ entries.push({ key, value: { kind: "string", text: v.text } });
18371
+ } else {
18372
+ return null;
18373
+ }
18374
+ }
18375
+ return { indexPropName, entries, defaultKey };
18376
+ }
18377
+
18378
+ // src/ssr-seed-plan.ts
18379
+ function classify2(name, origin, expr, parsed, available) {
18380
+ if (!isSupported(parsed).supported)
18381
+ return { kind: "opaque", name, origin };
18382
+ const frees = freeIdentifiers(parsed);
18383
+ if (frees === null)
18384
+ return { kind: "opaque", name, origin };
18385
+ for (const free of frees) {
18386
+ if (!available.has(free))
18387
+ return { kind: "opaque", name, origin };
18388
+ }
18389
+ return { kind: "derived", name, origin, expr, parsed, frees: [...frees] };
18390
+ }
18391
+ function computeSsrSeedPlan(metadata) {
18392
+ const baseScope = metadata.propsParams.map((p) => p.name);
18393
+ if (metadata.propsObjectName)
18394
+ baseScope.push(metadata.propsObjectName);
18395
+ for (const name of collectModuleStringConsts(metadata.localConstants).keys()) {
18396
+ baseScope.push(name);
18397
+ }
18398
+ const available = new Set(baseScope);
18399
+ const steps = [];
18400
+ for (const signal of metadata.signals) {
18401
+ if (signal.envReader) {
18402
+ const reader = envSignalReaderFor(signal.envReader);
18403
+ if (reader) {
18404
+ steps.push({ kind: "env-reader", name: signal.getter, reader });
18405
+ available.add(signal.getter);
18406
+ continue;
18407
+ }
18408
+ }
18409
+ const expr = signal.initialValue.trim();
18410
+ steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify2(signal.getter, "signal", expr, parseExpression(expr), available));
18411
+ available.add(signal.getter);
18412
+ }
18413
+ for (const memo of metadata.memos) {
18414
+ const body = extractArrowBodyExpression(memo.computation);
18415
+ const expr = body?.trim() ?? "";
18416
+ steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify2(memo.name, "memo", expr, memo.parsed ?? parseExpression(expr), available));
18417
+ available.add(memo.name);
18418
+ }
18419
+ return { baseScope, steps };
18420
+ }
18421
+
18422
+ // src/compiler.ts
18423
+ function mergeTemplateImports(lines) {
18424
+ const result = [];
18425
+ const valueIdx = new Map;
18426
+ const valueNames = new Map;
18427
+ const typeIdx = new Map;
18428
+ const typeNames = new Map;
18429
+ const seenOther = new Set;
18430
+ const fold = (src, rawNames, idx, names, render) => {
18431
+ if (!idx.has(src)) {
18432
+ idx.set(src, result.length);
18433
+ names.set(src, new Set);
18434
+ result.push("");
18435
+ }
18436
+ const set = names.get(src);
18437
+ for (const n of rawNames.split(",").map((s) => s.trim()).filter(Boolean))
18438
+ set.add(n);
18439
+ result[idx.get(src)] = render(src, set);
18440
+ };
18441
+ for (const raw of lines) {
18442
+ const line = raw.trim();
18443
+ if (!line)
18444
+ continue;
18445
+ const typeMatch = line.match(/^import\s+type\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/);
18446
+ const valueMatch = line.match(/^import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?$/);
18447
+ if (valueMatch) {
18448
+ fold(valueMatch[2], valueMatch[1], valueIdx, valueNames, (s, n) => `import { ${[...n].join(", ")} } from '${s}'`);
18449
+ } else if (typeMatch) {
18450
+ fold(typeMatch[2], typeMatch[1], typeIdx, typeNames, (s, n) => `import type { ${[...n].join(", ")} } from '${s}'`);
18451
+ } else if (!seenOther.has(line)) {
18452
+ seenOther.add(line);
18453
+ result.push(line);
18454
+ }
18455
+ }
18456
+ return result.filter(Boolean).join(`
18457
+ `);
18458
+ }
18459
+ function compileMultipleComponents(source, filePath, componentNames, options) {
18460
+ const files = [];
18461
+ const errors = [];
18462
+ const adapter = options.adapter;
18463
+ const entries = [];
18464
+ const program = options.program ?? (needsTypeBasedDetection(source) ? createProgramForFile(source, filePath)?.program : undefined);
18465
+ for (const componentName of componentNames) {
18466
+ const ctx = analyzeComponent(source, filePath, componentName, program);
18467
+ if (!ctx.jsxReturn) {
18468
+ errors.push(...ctx.errors);
18469
+ continue;
18470
+ }
18471
+ const ir = jsxToIR(ctx);
18472
+ errors.push(...ctx.errors);
18473
+ if (!ir)
18474
+ continue;
18475
+ const componentIR = {
18476
+ version: "0.1",
18477
+ metadata: buildMetadata(ctx),
18478
+ root: ir,
18479
+ errors: []
18480
+ };
18481
+ componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
18482
+ if (options.cssLayerPrefix) {
18483
+ applyCssLayerPrefix(componentIR, options.cssLayerPrefix);
18484
+ }
18485
+ entries.push({ componentIR, ctx });
18486
+ }
18487
+ if (options.outputIR) {
18488
+ for (const { componentIR } of entries) {
18489
+ const componentName = componentIR.metadata.componentName;
18490
+ files.push({
18491
+ path: filePath.replace(/\.tsx?$/, `.${componentName}.ir.json`),
18492
+ content: JSON.stringify(componentIR, null, 2),
18493
+ type: "ir"
18494
+ });
18495
+ }
18496
+ }
18497
+ const allOutputs = [];
18498
+ const defaultExportName = entries.find((e) => e.componentIR.metadata.hasDefaultExport)?.componentIR.metadata.componentName;
18499
+ const fileWideInlineExported = new Set;
18500
+ for (const { componentIR } of entries) {
18501
+ for (const name of collectInlineExportedNames(componentIR)) {
18502
+ fileWideInlineExported.add(name);
18503
+ }
18504
+ }
18505
+ const moduleConstantsSet = new Set;
18506
+ const moduleConstantsOrdered = [];
18507
+ const fileScope = computeFileScope(filePath);
18508
+ const nonExportedSiblings = new Set;
18509
+ for (const { componentIR } of entries) {
18510
+ if (!componentIR.metadata.isExported) {
18511
+ nonExportedSiblings.add(componentIR.metadata.componentName);
18512
+ }
18513
+ }
18514
+ setActiveComponentScope({ fileScope, nonExportedSiblings });
18515
+ const multiAdapterCaps = {
18516
+ templatePrimitives: options.adapter.templatePrimitives,
18517
+ acceptsTemplateCall: options.adapter.acceptsTemplateCall
18518
+ };
18519
+ try {
18520
+ for (const { componentIR } of entries) {
18521
+ const scriptBaseName = options.scriptBaseName ?? (!componentIR.metadata.hasDefaultExport && defaultExportName ? defaultExportName : undefined);
18522
+ const adapterOutput = adapter.generate(componentIR, {
18523
+ scriptBaseName,
18524
+ siblingTemplatesRegistered: options.siblingTemplatesRegistered,
18525
+ rewriteRelativeImport: options.rewriteRelativeImport
18526
+ });
18527
+ const moduleExports = generateModuleExports(componentIR, fileWideInlineExported, options.rewriteRelativeImport);
18528
+ const s = adapterOutput.sections;
18529
+ const imports = s.imports;
18530
+ const types = s.types;
18531
+ const component = s.component + (s.defaultExport || "");
18532
+ const mc = s.moduleConstants;
18533
+ if (mc && !moduleConstantsSet.has(mc)) {
18534
+ moduleConstantsSet.add(mc);
18535
+ moduleConstantsOrdered.push(mc);
18536
+ }
18537
+ allOutputs.push({
18538
+ componentName: componentIR.metadata.componentName,
18017
18539
  rawTemplate: adapterOutput.template,
18018
18540
  imports,
18019
18541
  types,
@@ -18209,7 +18731,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
18209
18731
  return { files, errors };
18210
18732
  }
18211
18733
  function buildMetadata(ctx) {
18212
- return {
18734
+ const metadata = {
18213
18735
  componentName: ctx.componentName || "Unknown",
18214
18736
  hasDefaultExport: ctx.hasDefaultExport,
18215
18737
  isExported: ctx.isExported,
@@ -18231,6 +18753,8 @@ function buildMetadata(ctx) {
18231
18753
  localFunctions: ctx.localFunctions,
18232
18754
  localConstants: ctx.localConstants
18233
18755
  };
18756
+ metadata.ssrSeedPlan = computeSsrSeedPlan(metadata);
18757
+ return metadata;
18234
18758
  }
18235
18759
  function compileJSX(source, filePath, options) {
18236
18760
  const files = [];
@@ -18393,7 +18917,7 @@ function compileJSX(source, filePath, options) {
18393
18917
  return { files, errors };
18394
18918
  }
18395
18919
  // src/shared-program.ts
18396
- import ts17 from "typescript";
18920
+ import ts18 from "typescript";
18397
18921
  function commonParent(paths) {
18398
18922
  if (paths.length === 0)
18399
18923
  return process.cwd();
@@ -18414,10 +18938,10 @@ function commonParent(paths) {
18414
18938
  function createProgramForCorpus(files, options = {}) {
18415
18939
  const baseUrl = options.baseUrl ?? commonParent(files);
18416
18940
  const compilerOptions = {
18417
- target: ts17.ScriptTarget.Latest,
18418
- module: ts17.ModuleKind.ESNext,
18419
- moduleResolution: ts17.ModuleResolutionKind.Bundler,
18420
- jsx: ts17.JsxEmit.ReactJSX,
18941
+ target: ts18.ScriptTarget.Latest,
18942
+ module: ts18.ModuleKind.ESNext,
18943
+ moduleResolution: ts18.ModuleResolutionKind.Bundler,
18944
+ jsx: ts18.JsxEmit.ReactJSX,
18421
18945
  strict: true,
18422
18946
  skipLibCheck: true,
18423
18947
  noEmit: true,
@@ -18427,7 +18951,7 @@ function createProgramForCorpus(files, options = {}) {
18427
18951
  ...options.compilerOptions
18428
18952
  };
18429
18953
  const absolute = files.map((f) => path_default.resolve(f));
18430
- return ts17.createProgram(absolute, compilerOptions, undefined, options.oldProgram);
18954
+ return ts18.createProgram(absolute, compilerOptions, undefined, options.oldProgram);
18431
18955
  }
18432
18956
  // src/adapters/interface.ts
18433
18957
  class BaseAdapter {
@@ -19095,7 +19619,7 @@ function emitAttrValue(value, emitter, name) {
19095
19619
  }
19096
19620
  }
19097
19621
  // src/combine-client-js.ts
19098
- import ts18 from "typescript";
19622
+ import ts19 from "typescript";
19099
19623
  var CHILD_PLACEHOLDER_RE = /import '\/\* @bf-child:(\w+) \*\/'/g;
19100
19624
  function combineParentChildClientJs(files) {
19101
19625
  const result = new Map;
@@ -19152,10 +19676,10 @@ function combineParentChildClientJs(files) {
19152
19676
  return result;
19153
19677
  }
19154
19678
  function parseAndMerge(content, importsBySource, otherImports, codeSections) {
19155
- const sourceFile = ts18.createSourceFile("combine.js", content, ts18.ScriptTarget.Latest, false, ts18.ScriptKind.JS);
19679
+ const sourceFile = ts19.createSourceFile("combine.js", content, ts19.ScriptTarget.Latest, false, ts19.ScriptKind.JS);
19156
19680
  const importSpans = [];
19157
19681
  for (const stmt of sourceFile.statements) {
19158
- if (!ts18.isImportDeclaration(stmt))
19682
+ if (!ts19.isImportDeclaration(stmt))
19159
19683
  continue;
19160
19684
  const start = stmt.getStart(sourceFile);
19161
19685
  const end = stmt.getEnd();
@@ -19165,8 +19689,8 @@ function parseAndMerge(content, importsBySource, otherImports, codeSections) {
19165
19689
  continue;
19166
19690
  const clause = stmt.importClause;
19167
19691
  const bindings = clause?.namedBindings;
19168
- const specifier = ts18.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
19169
- if (clause && !clause.name && bindings && ts18.isNamedImports(bindings)) {
19692
+ const specifier = ts19.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
19693
+ if (clause && !clause.name && bindings && ts19.isNamedImports(bindings)) {
19170
19694
  if (!importsBySource.has(specifier)) {
19171
19695
  importsBySource.set(specifier, new Set);
19172
19696
  }
@@ -19325,7 +19849,7 @@ function escapeRe(s) {
19325
19849
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19326
19850
  }
19327
19851
  // src/debug.ts
19328
- import ts19 from "typescript";
19852
+ import ts20 from "typescript";
19329
19853
  function buildComponentGraph(source, filePath, componentName) {
19330
19854
  const ctx = analyzeComponent(source, filePath, componentName);
19331
19855
  if (!ctx.jsxReturn) {
@@ -20610,7 +21134,7 @@ function truncateExpr(expr, max = 40) {
20610
21134
  function exprReadsPropMember(expr, propsObjectName) {
20611
21135
  let sf;
20612
21136
  try {
20613
- sf = ts19.createSourceFile("__attr.tsx", `(${expr})`, ts19.ScriptTarget.Latest, true, ts19.ScriptKind.TSX);
21137
+ sf = ts20.createSourceFile("__attr.tsx", `(${expr})`, ts20.ScriptTarget.Latest, true, ts20.ScriptKind.TSX);
20614
21138
  } catch {
20615
21139
  return false;
20616
21140
  }
@@ -20618,11 +21142,11 @@ function exprReadsPropMember(expr, propsObjectName) {
20618
21142
  const visit3 = (n) => {
20619
21143
  if (found)
20620
21144
  return;
20621
- if (ts19.isPropertyAccessExpression(n) && ts19.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
21145
+ if (ts20.isPropertyAccessExpression(n) && ts20.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
20622
21146
  found = true;
20623
21147
  return;
20624
21148
  }
20625
- ts19.forEachChild(n, visit3);
21149
+ ts20.forEachChild(n, visit3);
20626
21150
  };
20627
21151
  visit3(sf);
20628
21152
  return found;
@@ -20692,7 +21216,7 @@ function findSourceFile2(meta) {
20692
21216
  return null;
20693
21217
  }
20694
21218
  // src/profiler.ts
20695
- import ts20 from "typescript";
21219
+ import ts21 from "typescript";
20696
21220
  var PROFILE_SCHEMA_VERSION = 1;
20697
21221
  var DEFAULT_FANOUT_THRESHOLD = 8;
20698
21222
  function buildStaticBudget(source, filePath, componentName, options = {}) {
@@ -20962,15 +21486,15 @@ function joinProfilerEvents(events, index) {
20962
21486
  return { joined, unattributed, diagnostics };
20963
21487
  }
20964
21488
  function findUninstrumentedEffects(source, filePath, instrumentedLines) {
20965
- const sf = ts20.createSourceFile(filePath, source, ts20.ScriptTarget.Latest, true, ts20.ScriptKind.TSX);
21489
+ const sf = ts21.createSourceFile(filePath, source, ts21.ScriptTarget.Latest, true, ts21.ScriptKind.TSX);
20966
21490
  const out = [];
20967
21491
  const visit3 = (node) => {
20968
- if (ts20.isCallExpression(node) && ts20.isIdentifier(node.expression) && node.expression.text === "createEffect") {
21492
+ if (ts21.isCallExpression(node) && ts21.isIdentifier(node.expression) && node.expression.text === "createEffect") {
20969
21493
  const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
20970
21494
  if (!instrumentedLines.has(line))
20971
21495
  out.push({ file: filePath, line });
20972
21496
  }
20973
- ts20.forEachChild(node, visit3);
21497
+ ts21.forEachChild(node, visit3);
20974
21498
  };
20975
21499
  visit3(sf);
20976
21500
  out.sort((a, b) => a.line - b.line);
@@ -21278,13 +21802,13 @@ function assessBatchSafety(args) {
21278
21802
  const signalGetters = new Set(args.graph.signals.map((s) => s.name));
21279
21803
  let sf;
21280
21804
  try {
21281
- sf = ts20.createSourceFile("__h.ts", `const __h = ${args.handler}`, ts20.ScriptTarget.Latest, true);
21805
+ sf = ts21.createSourceFile("__h.ts", `const __h = ${args.handler}`, ts21.ScriptTarget.Latest, true);
21282
21806
  } catch {
21283
21807
  return "unverified";
21284
21808
  }
21285
21809
  const calls = [];
21286
21810
  const visit3 = (node) => {
21287
- if (ts20.isCallExpression(node) && ts20.isIdentifier(node.expression)) {
21811
+ if (ts21.isCallExpression(node) && ts21.isIdentifier(node.expression)) {
21288
21812
  const name = node.expression.text;
21289
21813
  if (setters.has(name))
21290
21814
  calls.push({ pos: node.getStart(sf), kind: "write" });
@@ -21293,7 +21817,7 @@ function assessBatchSafety(args) {
21293
21817
  else if (!signalGetters.has(name) && !memoNames.has(name))
21294
21818
  calls.push({ pos: node.getStart(sf), kind: "risky" });
21295
21819
  }
21296
- ts20.forEachChild(node, visit3);
21820
+ ts21.forEachChild(node, visit3);
21297
21821
  };
21298
21822
  visit3(sf);
21299
21823
  calls.sort((a, b) => a.pos - b.pos);
@@ -21664,602 +22188,267 @@ function computeMetrics(graph, eventSummary, hydrated) {
21664
22188
  }
21665
22189
  function computeMaxMemoChainDepth(graph) {
21666
22190
  if (graph.memos.length === 0)
21667
- return 0;
21668
- const memoSet = new Set(graph.memos.map((m) => m.name));
21669
- const memoDeps = new Map;
21670
- for (const memo of graph.memos) {
21671
- memoDeps.set(memo.name, memo.deps.filter((d) => memoSet.has(d)));
21672
- }
21673
- const cache = new Map;
21674
- function depth(name, visited) {
21675
- if (cache.has(name))
21676
- return cache.get(name);
21677
- if (visited.has(name))
21678
- return 0;
21679
- const children = memoDeps.get(name) ?? [];
21680
- if (children.length === 0) {
21681
- cache.set(name, 1);
21682
- return 1;
21683
- }
21684
- visited.add(name);
21685
- const d = 1 + Math.max(...children.map((c) => depth(c, new Set(visited))));
21686
- cache.set(name, d);
21687
- return d;
21688
- }
21689
- let max = 0;
21690
- for (const memo of graph.memos) {
21691
- const d = depth(memo.name, new Set);
21692
- if (d > max)
21693
- max = d;
21694
- }
21695
- return max;
21696
- }
21697
- function computeFindings(metrics, graph, eventSummary) {
21698
- const findings = [];
21699
- for (const signal of graph.signals) {
21700
- if (signal.consumers.length > THRESHOLDS.highFanOut) {
21701
- findings.push({
21702
- kind: "high-fan-out",
21703
- severity: "warning",
21704
- signal: signal.name,
21705
- message: `${signal.name} has ${signal.consumers.length} consumers (fan-out > ${THRESHOLDS.highFanOut})`,
21706
- suggestion: `Split ${signal.name} into finer-grained signals, or add a createMemo to shield downstream consumers from unrelated updates`,
21707
- loc: { file: signal.loc.file, line: signal.loc.line }
21708
- });
21709
- }
21710
- }
21711
- if (metrics.maxMemoChainDepth > THRESHOLDS.deepMemoChain) {
21712
- findings.push({
21713
- kind: "deep-memo-chain",
21714
- severity: "warning",
21715
- depth: metrics.maxMemoChainDepth,
21716
- message: `Memo chain depth ${metrics.maxMemoChainDepth} (threshold: ${THRESHOLDS.deepMemoChain}) — a single signal update cascades through ${metrics.maxMemoChainDepth} memo levels`,
21717
- suggestion: "Flatten intermediate memos that do not cache expensive computations; deep chains increase propagation latency"
21718
- });
21719
- }
21720
- const batchSeen = new Set;
21721
- for (const event of eventSummary.events) {
21722
- const distinct = new Set;
21723
- const setterNames = [];
21724
- for (const sc of event.setterCalls) {
21725
- if (sc.signal) {
21726
- distinct.add(sc.signal);
21727
- setterNames.push(sc.setter);
21728
- }
21729
- }
21730
- if (distinct.size >= THRESHOLDS.batchMinSignals) {
21731
- const dedupeKey = `${event.eventName}|${event.loc.file ?? ""}|${event.loc.start.line}|${[...distinct].sort().join(",")}`;
21732
- if (batchSeen.has(dedupeKey))
21733
- continue;
21734
- batchSeen.add(dedupeKey);
21735
- findings.push({
21736
- kind: "batch-candidate",
21737
- severity: "info",
21738
- signals: [...distinct],
21739
- message: `${event.eventName} on <${event.elementContext}> sets ${distinct.size} signals (${[...distinct].join(", ")}) — triggers ${distinct.size} separate update cycles (static; verify setters are not in separate if/else branches)`,
21740
- suggestion: `If all listed setters fire unconditionally in the same handler path, wrap in batch(() => { ${setterNames.join("; ")}; }) to collapse ${distinct.size} cycles into 1`,
21741
- loc: event.loc.file ? { file: event.loc.file, line: event.loc.start.line } : undefined
21742
- });
21743
- }
21744
- }
21745
- if (metrics.dynamicBindings > 0 && metrics.fallbacks >= THRESHOLDS.fallbackHeavyMin && metrics.fallbacks / metrics.dynamicBindings > THRESHOLDS.fallbackHeavyRatio) {
21746
- findings.push({
21747
- kind: "fallback-heavy",
21748
- severity: "info",
21749
- message: `${metrics.fallbacks}/${metrics.dynamicBindings} bindings (${Math.round(metrics.fallbacks / metrics.dynamicBindings * 100)}%) are fallback-wrapped — reactivity not statically provable`,
21750
- suggestion: "Run `bf debug fallbacks` to see each expression and fix them so the compiler can prove reactivity without the fallback wrapper"
21751
- });
21752
- }
21753
- return findings;
21754
- }
21755
- function diffProfiles(before, after) {
21756
- const worseWhenHigher = new Set([
21757
- "fallbacks",
21758
- "maxSignalFanOut",
21759
- "maxMemoChainDepth",
21760
- "totalSubscriptions",
21761
- "batchCandidateCount"
21762
- ]);
21763
- const numericKeys = [
21764
- "signals",
21765
- "memos",
21766
- "effects",
21767
- "loops",
21768
- "eventHandlers",
21769
- "dynamicBindings",
21770
- "fallbacks",
21771
- "conditionals",
21772
- "maxSignalFanOut",
21773
- "maxMemoChainDepth",
21774
- "totalSubscriptions",
21775
- "batchCandidateCount"
21776
- ];
21777
- const regressions = [];
21778
- const improvements = [];
21779
- const neutral = [];
21780
- for (const key of numericKeys) {
21781
- const b = before[key];
21782
- const a = after[key];
21783
- if (a === b)
21784
- continue;
21785
- const entry = { metric: key, before: b, after: a, delta: a - b };
21786
- if (worseWhenHigher.has(key)) {
21787
- if (a > b)
21788
- regressions.push(entry);
21789
- else
21790
- improvements.push(entry);
21791
- } else {
21792
- neutral.push(entry);
21793
- }
21794
- }
21795
- return { componentName: after.componentName, before, after, regressions, improvements, neutral };
21796
- }
21797
- function formatSingleProfile(profile) {
21798
- const m = profile.metrics;
21799
- const lines = [];
21800
- lines.push(`${m.componentName} — reactive profile (static)`);
21801
- if (m.sourceFile)
21802
- lines.push(` source: ${m.sourceFile}`);
21803
- lines.push(` hydrated: ${m.hydrated ? "yes" : "no"}`);
21804
- lines.push("");
21805
- lines.push(" Counts:");
21806
- lines.push(` signals: ${m.signals}`);
21807
- lines.push(` memos: ${m.memos}`);
21808
- if (m.effects > 0)
21809
- lines.push(` effects: ${m.effects}`);
21810
- lines.push(` dynamic bindings: ${m.dynamicBindings}`);
21811
- if (m.fallbacks > 0)
21812
- lines.push(` fallbacks: ${m.fallbacks}`);
21813
- if (m.loops > 0)
21814
- lines.push(` loops: ${m.loops}`);
21815
- if (m.conditionals > 0)
21816
- lines.push(` conditionals: ${m.conditionals}`);
21817
- if (m.eventHandlers > 0)
21818
- lines.push(` event handlers: ${m.eventHandlers}`);
21819
- lines.push("");
21820
- lines.push(" Reactive budget (SR5):");
21821
- const fanOutSuffix = m.hotSignal ? ` (${m.hotSignal})` : "";
21822
- lines.push(` max signal fan-out: ${m.maxSignalFanOut}${fanOutSuffix}`);
21823
- lines.push(` max memo chain depth: ${m.maxMemoChainDepth}`);
21824
- lines.push(` total subscriptions: ${m.totalSubscriptions}`);
21825
- if (m.batchCandidateCount > 0) {
21826
- lines.push(` batch candidates: ${m.batchCandidateCount} handler(s) set ≥2 signals`);
21827
- }
21828
- if (profile.findings.length > 0) {
21829
- lines.push("");
21830
- lines.push(" Findings:");
21831
- for (const f of profile.findings) {
21832
- const icon = f.severity === "warning" ? "⚠" : "→";
21833
- lines.push(` ${icon} [${f.kind}] ${f.message}`);
21834
- lines.push(` fix: ${f.suggestion}`);
21835
- if (f.loc) {
21836
- const file = f.loc.file.split("/").pop() ?? f.loc.file;
21837
- lines.push(` at ${file}:${f.loc.line}`);
21838
- }
21839
- }
21840
- } else {
21841
- lines.push("");
21842
- lines.push(" No findings — component is within all thresholds.");
21843
- }
21844
- return lines.join(`
21845
- `);
21846
- }
21847
- function formatProfileTable(profiles) {
21848
- if (profiles.length === 0)
21849
- return "No components found.";
21850
- const sorted = [...profiles].sort((a, b) => b.metrics.totalSubscriptions - a.metrics.totalSubscriptions);
21851
- const lines = [];
21852
- lines.push("Component sig memo bind fall fanOut chain subs batch findings");
21853
- lines.push("─".repeat(90));
21854
- for (const p of sorted) {
21855
- const m = p.metrics;
21856
- const name = m.componentName.padEnd(23).slice(0, 23);
21857
- const findingStr = p.findings.length > 0 ? p.findings.map((f) => f.kind.replace(/-/g, "_")).join(",") : "—";
21858
- const row = [
21859
- name,
21860
- String(m.signals).padStart(3),
21861
- String(m.memos).padStart(5),
21862
- String(m.dynamicBindings).padStart(5),
21863
- String(m.fallbacks).padStart(5),
21864
- String(m.maxSignalFanOut).padStart(7),
21865
- String(m.maxMemoChainDepth).padStart(6),
21866
- String(m.totalSubscriptions).padStart(5),
21867
- String(m.batchCandidateCount).padStart(6),
21868
- ` ${findingStr}`
21869
- ].join(" ");
21870
- lines.push(row);
21871
- }
21872
- const allFindings = sorted.flatMap((p) => p.findings.map((f) => ({ component: p.metrics.componentName, finding: f })));
21873
- if (allFindings.length > 0) {
21874
- lines.push("");
21875
- lines.push("Findings:");
21876
- for (const { component, finding } of allFindings) {
21877
- const icon = finding.severity === "warning" ? "⚠" : "→";
21878
- lines.push(` ${icon} ${component}: ${finding.message}`);
21879
- lines.push(` fix: ${finding.suggestion}`);
21880
- if (finding.loc) {
21881
- const file = finding.loc.file.split("/").pop() ?? finding.loc.file;
21882
- lines.push(` at ${file}:${finding.loc.line}`);
21883
- }
21884
- }
21885
- } else {
21886
- lines.push("");
21887
- lines.push("No findings across all components.");
21888
- }
21889
- return lines.join(`
21890
- `);
21891
- }
21892
- function formatProfileDiff(diff) {
21893
- const lines = [];
21894
- lines.push(`${diff.componentName} — reactive profile diff (before → after)`);
21895
- lines.push("");
21896
- if (diff.regressions.length === 0 && diff.improvements.length === 0 && diff.neutral.length === 0) {
21897
- lines.push(" No changes in reactive metrics.");
21898
- return lines.join(`
21899
- `);
21900
- }
21901
- if (diff.regressions.length > 0) {
21902
- lines.push(" Regressions (reactive cost increased):");
21903
- for (const e of diff.regressions) {
21904
- lines.push(` ${e.metric}: ${e.before} → ${e.after} (+${e.delta})`);
21905
- }
22191
+ return 0;
22192
+ const memoSet = new Set(graph.memos.map((m) => m.name));
22193
+ const memoDeps = new Map;
22194
+ for (const memo of graph.memos) {
22195
+ memoDeps.set(memo.name, memo.deps.filter((d) => memoSet.has(d)));
21906
22196
  }
21907
- if (diff.improvements.length > 0) {
21908
- lines.push(" Improvements (reactive cost decreased):");
21909
- for (const e of diff.improvements) {
21910
- lines.push(` ${e.metric}: ${e.before} → ${e.after} (${e.delta})`);
22197
+ const cache = new Map;
22198
+ function depth(name, visited) {
22199
+ if (cache.has(name))
22200
+ return cache.get(name);
22201
+ if (visited.has(name))
22202
+ return 0;
22203
+ const children = memoDeps.get(name) ?? [];
22204
+ if (children.length === 0) {
22205
+ cache.set(name, 1);
22206
+ return 1;
21911
22207
  }
22208
+ visited.add(name);
22209
+ const d = 1 + Math.max(...children.map((c) => depth(c, new Set(visited))));
22210
+ cache.set(name, d);
22211
+ return d;
21912
22212
  }
21913
- if (diff.neutral.length > 0) {
21914
- lines.push(" Structural changes (count changes, no clear direction):");
21915
- for (const e of diff.neutral) {
21916
- const sign = (e.delta ?? 0) > 0 ? "+" : "";
21917
- lines.push(` ${e.metric}: ${e.before} → ${e.after} (${sign}${e.delta})`);
21918
- }
22213
+ let max = 0;
22214
+ for (const memo of graph.memos) {
22215
+ const d = depth(memo.name, new Set);
22216
+ if (d > max)
22217
+ max = d;
21919
22218
  }
21920
- return lines.join(`
21921
- `);
21922
- }
21923
- function profileToJSON(profile) {
21924
- return {
21925
- metrics: profile.metrics,
21926
- findings: profile.findings
21927
- };
22219
+ return max;
21928
22220
  }
21929
- // src/augment-inherited-props.ts
21930
- import ts21 from "typescript";
21931
- function collectContextConsumers(metadata) {
21932
- const constants = metadata.localConstants ?? [];
21933
- const contextDefaults = new Map;
21934
- for (const c of constants) {
21935
- if (c.systemConstructKind !== "createContext" || c.value === undefined)
21936
- continue;
21937
- contextDefaults.set(c.name, parseCreateContextDefault(c.value));
22221
+ function computeFindings(metrics, graph, eventSummary) {
22222
+ const findings = [];
22223
+ for (const signal of graph.signals) {
22224
+ if (signal.consumers.length > THRESHOLDS.highFanOut) {
22225
+ findings.push({
22226
+ kind: "high-fan-out",
22227
+ severity: "warning",
22228
+ signal: signal.name,
22229
+ message: `${signal.name} has ${signal.consumers.length} consumers (fan-out > ${THRESHOLDS.highFanOut})`,
22230
+ suggestion: `Split ${signal.name} into finer-grained signals, or add a createMemo to shield downstream consumers from unrelated updates`,
22231
+ loc: { file: signal.loc.file, line: signal.loc.line }
22232
+ });
22233
+ }
21938
22234
  }
21939
- if (contextDefaults.size === 0)
21940
- return [];
21941
- const consumers = [];
21942
- for (const c of constants) {
21943
- if (c.value === undefined)
21944
- continue;
21945
- const ctxName = parseUseContextArg(c.value);
21946
- if (ctxName === null || !contextDefaults.has(ctxName))
21947
- continue;
21948
- consumers.push({
21949
- localName: c.name,
21950
- contextName: ctxName,
21951
- defaultValue: contextDefaults.get(ctxName) ?? null
22235
+ if (metrics.maxMemoChainDepth > THRESHOLDS.deepMemoChain) {
22236
+ findings.push({
22237
+ kind: "deep-memo-chain",
22238
+ severity: "warning",
22239
+ depth: metrics.maxMemoChainDepth,
22240
+ message: `Memo chain depth ${metrics.maxMemoChainDepth} (threshold: ${THRESHOLDS.deepMemoChain}) — a single signal update cascades through ${metrics.maxMemoChainDepth} memo levels`,
22241
+ suggestion: "Flatten intermediate memos that do not cache expensive computations; deep chains increase propagation latency"
21952
22242
  });
21953
22243
  }
21954
- return consumers;
21955
- }
21956
- function parseUseContextArg(source) {
21957
- const expr = parseSingleExpression(source);
21958
- if (!expr || !ts21.isCallExpression(expr))
21959
- return null;
21960
- if (!ts21.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
21961
- return null;
21962
- if (expr.arguments.length !== 1)
21963
- return null;
21964
- const arg = expr.arguments[0];
21965
- return ts21.isIdentifier(arg) ? arg.text : null;
21966
- }
21967
- function parseCreateContextDefault(source) {
21968
- const expr = parseSingleExpression(source);
21969
- if (!expr || !ts21.isCallExpression(expr))
21970
- return null;
21971
- if (expr.arguments.length === 0)
21972
- return null;
21973
- const arg = expr.arguments[0];
21974
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
21975
- return arg.text;
21976
- if (ts21.isNumericLiteral(arg))
21977
- return Number(arg.text);
21978
- if (arg.kind === ts21.SyntaxKind.TrueKeyword)
21979
- return true;
21980
- if (arg.kind === ts21.SyntaxKind.FalseKeyword)
21981
- return false;
21982
- return null;
21983
- }
21984
- function parseSingleExpression(source) {
21985
- const sf = ts21.createSourceFile("__ctx.ts", `(${source})`, ts21.ScriptTarget.Latest, false);
21986
- const stmt = sf.statements[0];
21987
- if (!stmt || !ts21.isExpressionStatement(stmt))
21988
- return null;
21989
- let e = stmt.expression;
21990
- while (ts21.isParenthesizedExpression(e))
21991
- e = e.expression;
21992
- return e;
21993
- }
21994
- function augmentInheritedPropAccesses(ir) {
21995
- const propsObj = ir.metadata.propsObjectName;
21996
- if (!propsObj)
21997
- return;
21998
- const existing = new Set(ir.metadata.propsParams.map((p) => p.name));
21999
- const bareRefProps = new Set;
22000
- const booleanAttrProps = new Set;
22001
- const accessed = new Set;
22002
- const accessRe = new RegExp(`(?:^|[^\\w$.])${propsObj}\\.([A-Za-z_$][\\w$]*)`, "g");
22003
- const scan = (s) => {
22004
- if (!s)
22005
- return;
22006
- for (const m of s.matchAll(accessRe))
22007
- accessed.add(m[1]);
22008
- };
22009
- for (const memo of ir.metadata.memos)
22010
- scan(memo.computation);
22011
- for (const signal of ir.metadata.signals)
22012
- scan(signal.initialValue);
22013
- for (const stmt of ir.metadata.initStatements ?? [])
22014
- scan(stmt.body);
22015
- for (const eff of ir.metadata.effects ?? [])
22016
- scan(eff.body);
22017
- for (const c of ir.metadata.localConstants ?? []) {
22018
- if (c.isModule)
22019
- continue;
22020
- scan(c.value);
22021
- }
22022
- const walk = (node) => {
22023
- if (!node)
22024
- return;
22025
- const el = node;
22026
- for (const attr of el.attrs ?? []) {
22027
- const v = attr.value;
22028
- if (v?.parts) {
22029
- for (const part of v.parts) {
22030
- if (part.type === "string")
22031
- scan(part.value);
22032
- else if (part.type === "ternary") {
22033
- scan(part.condition);
22034
- scan(part.whenTrue);
22035
- scan(part.whenFalse);
22036
- } else if (part.type === "lookup")
22037
- scan(part.key);
22038
- }
22039
- }
22040
- if (v?.kind === "expression" && typeof v.expr === "string") {
22041
- scan(v.expr);
22042
- const expr = v.expr.trim();
22043
- const prefix = `${propsObj}.`;
22044
- if (isBooleanAttr(attr.name) || v.presenceOrUndefined) {
22045
- const m = expr.match(new RegExp(`^${propsObj}\\.([A-Za-z_$][\\w$]*)`));
22046
- if (m)
22047
- booleanAttrProps.add(m[1]);
22048
- } else if (expr.startsWith(prefix)) {
22049
- const rest = expr.slice(prefix.length);
22050
- if (/^[A-Za-z_$][\w$]*$/.test(rest))
22051
- bareRefProps.add(rest);
22052
- }
22244
+ const batchSeen = new Set;
22245
+ for (const event of eventSummary.events) {
22246
+ const distinct = new Set;
22247
+ const setterNames = [];
22248
+ for (const sc of event.setterCalls) {
22249
+ if (sc.signal) {
22250
+ distinct.add(sc.signal);
22251
+ setterNames.push(sc.setter);
22053
22252
  }
22054
22253
  }
22055
- for (const child of el.children ?? []) {
22056
- const c = child;
22057
- walk(c.element ?? child);
22254
+ if (distinct.size >= THRESHOLDS.batchMinSignals) {
22255
+ const dedupeKey = `${event.eventName}|${event.loc.file ?? ""}|${event.loc.start.line}|${[...distinct].sort().join(",")}`;
22256
+ if (batchSeen.has(dedupeKey))
22257
+ continue;
22258
+ batchSeen.add(dedupeKey);
22259
+ findings.push({
22260
+ kind: "batch-candidate",
22261
+ severity: "info",
22262
+ signals: [...distinct],
22263
+ message: `${event.eventName} on <${event.elementContext}> sets ${distinct.size} signals (${[...distinct].join(", ")}) — triggers ${distinct.size} separate update cycles (static; verify setters are not in separate if/else branches)`,
22264
+ suggestion: `If all listed setters fire unconditionally in the same handler path, wrap in batch(() => { ${setterNames.join("; ")}; }) to collapse ${distinct.size} cycles into 1`,
22265
+ loc: event.loc.file ? { file: event.loc.file, line: event.loc.start.line } : undefined
22266
+ });
22058
22267
  }
22059
- const branchy = node;
22060
- walk(branchy.whenTrue);
22061
- walk(branchy.whenFalse);
22062
- walk(branchy.consequent);
22063
- walk(branchy.alternate);
22064
- };
22065
- walk(ir.root);
22066
- for (const name of accessed) {
22067
- if (existing.has(name))
22068
- continue;
22069
- let raw;
22070
- if (booleanAttrProps.has(name))
22071
- raw = "boolean";
22072
- else if (bareRefProps.has(name))
22073
- raw = "unknown";
22074
- else
22075
- raw = "string";
22076
- const type = raw === "boolean" ? { kind: "primitive", raw: "boolean", primitive: "boolean" } : raw === "string" ? { kind: "primitive", raw: "string", primitive: "string" } : { kind: "unknown", raw: "unknown" };
22077
- ir.metadata.propsParams.push({ name, type, optional: true });
22078
- existing.add(name);
22079
22268
  }
22269
+ if (metrics.dynamicBindings > 0 && metrics.fallbacks >= THRESHOLDS.fallbackHeavyMin && metrics.fallbacks / metrics.dynamicBindings > THRESHOLDS.fallbackHeavyRatio) {
22270
+ findings.push({
22271
+ kind: "fallback-heavy",
22272
+ severity: "info",
22273
+ message: `${metrics.fallbacks}/${metrics.dynamicBindings} bindings (${Math.round(metrics.fallbacks / metrics.dynamicBindings * 100)}%) are fallback-wrapped — reactivity not statically provable`,
22274
+ suggestion: "Run `bf debug fallbacks` to see each expression and fix them so the compiler can prove reactivity without the fallback wrapper"
22275
+ });
22276
+ }
22277
+ return findings;
22080
22278
  }
22081
- function parseStaticStringConst(source) {
22082
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
22083
- const stmt = sf.statements[0];
22084
- if (!stmt || !ts21.isVariableStatement(stmt))
22085
- return null;
22086
- let init = stmt.declarationList.declarations[0]?.initializer;
22087
- while (init && ts21.isParenthesizedExpression(init))
22088
- init = init.expression;
22089
- if (!init)
22090
- return null;
22091
- if (ts21.isStringLiteral(init) || ts21.isNoSubstitutionTemplateLiteral(init)) {
22092
- return init.text;
22279
+ function diffProfiles(before, after) {
22280
+ const worseWhenHigher = new Set([
22281
+ "fallbacks",
22282
+ "maxSignalFanOut",
22283
+ "maxMemoChainDepth",
22284
+ "totalSubscriptions",
22285
+ "batchCandidateCount"
22286
+ ]);
22287
+ const numericKeys = [
22288
+ "signals",
22289
+ "memos",
22290
+ "effects",
22291
+ "loops",
22292
+ "eventHandlers",
22293
+ "dynamicBindings",
22294
+ "fallbacks",
22295
+ "conditionals",
22296
+ "maxSignalFanOut",
22297
+ "maxMemoChainDepth",
22298
+ "totalSubscriptions",
22299
+ "batchCandidateCount"
22300
+ ];
22301
+ const regressions = [];
22302
+ const improvements = [];
22303
+ const neutral = [];
22304
+ for (const key of numericKeys) {
22305
+ const b = before[key];
22306
+ const a = after[key];
22307
+ if (a === b)
22308
+ continue;
22309
+ const entry = { metric: key, before: b, after: a, delta: a - b };
22310
+ if (worseWhenHigher.has(key)) {
22311
+ if (a > b)
22312
+ regressions.push(entry);
22313
+ else
22314
+ improvements.push(entry);
22315
+ } else {
22316
+ neutral.push(entry);
22317
+ }
22093
22318
  }
22094
- return evalStringArrayJoin(source);
22319
+ return { componentName: after.componentName, before, after, regressions, improvements, neutral };
22095
22320
  }
22096
- function evalTemplateOfStringConsts(source, resolved) {
22097
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
22098
- const stmt = sf.statements[0];
22099
- if (!stmt || !ts21.isVariableStatement(stmt))
22100
- return null;
22101
- let init = stmt.declarationList.declarations[0]?.initializer;
22102
- while (init && ts21.isParenthesizedExpression(init))
22103
- init = init.expression;
22104
- if (!init || !ts21.isTemplateExpression(init))
22105
- return null;
22106
- let out = init.head.text;
22107
- for (const span of init.templateSpans) {
22108
- if (!ts21.isIdentifier(span.expression))
22109
- return null;
22110
- const value = resolved.get(span.expression.text);
22111
- if (value === undefined)
22112
- return null;
22113
- out += value + span.literal.text;
22321
+ function formatSingleProfile(profile) {
22322
+ const m = profile.metrics;
22323
+ const lines = [];
22324
+ lines.push(`${m.componentName} reactive profile (static)`);
22325
+ if (m.sourceFile)
22326
+ lines.push(` source: ${m.sourceFile}`);
22327
+ lines.push(` hydrated: ${m.hydrated ? "yes" : "no"}`);
22328
+ lines.push("");
22329
+ lines.push(" Counts:");
22330
+ lines.push(` signals: ${m.signals}`);
22331
+ lines.push(` memos: ${m.memos}`);
22332
+ if (m.effects > 0)
22333
+ lines.push(` effects: ${m.effects}`);
22334
+ lines.push(` dynamic bindings: ${m.dynamicBindings}`);
22335
+ if (m.fallbacks > 0)
22336
+ lines.push(` fallbacks: ${m.fallbacks}`);
22337
+ if (m.loops > 0)
22338
+ lines.push(` loops: ${m.loops}`);
22339
+ if (m.conditionals > 0)
22340
+ lines.push(` conditionals: ${m.conditionals}`);
22341
+ if (m.eventHandlers > 0)
22342
+ lines.push(` event handlers: ${m.eventHandlers}`);
22343
+ lines.push("");
22344
+ lines.push(" Reactive budget (SR5):");
22345
+ const fanOutSuffix = m.hotSignal ? ` (${m.hotSignal})` : "";
22346
+ lines.push(` max signal fan-out: ${m.maxSignalFanOut}${fanOutSuffix}`);
22347
+ lines.push(` max memo chain depth: ${m.maxMemoChainDepth}`);
22348
+ lines.push(` total subscriptions: ${m.totalSubscriptions}`);
22349
+ if (m.batchCandidateCount > 0) {
22350
+ lines.push(` batch candidates: ${m.batchCandidateCount} handler(s) set ≥2 signals`);
22114
22351
  }
22115
- return out;
22116
- }
22117
- function collectModuleStringConsts(constants) {
22118
- const map = new Map;
22119
- const candidates = (constants ?? []).filter((c) => c.isModule && c.value !== undefined);
22120
- let progressed = true;
22121
- while (progressed) {
22122
- progressed = false;
22123
- for (const c of candidates) {
22124
- if (map.has(c.name))
22125
- continue;
22126
- const literal = parseStaticStringConst(c.value) ?? evalTemplateOfStringConsts(c.value, map);
22127
- if (literal !== null) {
22128
- map.set(c.name, literal);
22129
- progressed = true;
22352
+ if (profile.findings.length > 0) {
22353
+ lines.push("");
22354
+ lines.push(" Findings:");
22355
+ for (const f of profile.findings) {
22356
+ const icon = f.severity === "warning" ? "⚠" : "→";
22357
+ lines.push(` ${icon} [${f.kind}] ${f.message}`);
22358
+ lines.push(` fix: ${f.suggestion}`);
22359
+ if (f.loc) {
22360
+ const file = f.loc.file.split("/").pop() ?? f.loc.file;
22361
+ lines.push(` at ${file}:${f.loc.line}`);
22130
22362
  }
22131
22363
  }
22364
+ } else {
22365
+ lines.push("");
22366
+ lines.push(" No findings — component is within all thresholds.");
22132
22367
  }
22133
- return map;
22368
+ return lines.join(`
22369
+ `);
22134
22370
  }
22135
- function lookupStaticRecordLiteral(objectName, key, constants) {
22136
- const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
22137
- if (constInfo?.value === undefined)
22138
- return null;
22139
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
22140
- if (sf.statements.length !== 1)
22141
- return null;
22142
- const stmt = sf.statements[0];
22143
- if (!ts21.isExpressionStatement(stmt))
22144
- return null;
22145
- let parsed = stmt.expression;
22146
- while (ts21.isParenthesizedExpression(parsed))
22147
- parsed = parsed.expression;
22148
- if (!ts21.isObjectLiteralExpression(parsed))
22149
- return null;
22150
- for (const prop of parsed.properties) {
22151
- if (!ts21.isPropertyAssignment(prop))
22152
- continue;
22153
- const name = prop.name;
22154
- const propKey = ts21.isIdentifier(name) || ts21.isStringLiteral(name) || ts21.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
22155
- if (propKey !== key)
22156
- continue;
22157
- let v = prop.initializer;
22158
- while (ts21.isParenthesizedExpression(v))
22159
- v = v.expression;
22160
- if (ts21.isNumericLiteral(v))
22161
- return { kind: "number", text: v.text };
22162
- if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
22163
- return { kind: "string", text: v.text };
22164
- }
22165
- return null;
22371
+ function formatProfileTable(profiles) {
22372
+ if (profiles.length === 0)
22373
+ return "No components found.";
22374
+ const sorted = [...profiles].sort((a, b) => b.metrics.totalSubscriptions - a.metrics.totalSubscriptions);
22375
+ const lines = [];
22376
+ lines.push("Component sig memo bind fall fanOut chain subs batch findings");
22377
+ lines.push("─".repeat(90));
22378
+ for (const p of sorted) {
22379
+ const m = p.metrics;
22380
+ const name = m.componentName.padEnd(23).slice(0, 23);
22381
+ const findingStr = p.findings.length > 0 ? p.findings.map((f) => f.kind.replace(/-/g, "_")).join(",") : "—";
22382
+ const row = [
22383
+ name,
22384
+ String(m.signals).padStart(3),
22385
+ String(m.memos).padStart(5),
22386
+ String(m.dynamicBindings).padStart(5),
22387
+ String(m.fallbacks).padStart(5),
22388
+ String(m.maxSignalFanOut).padStart(7),
22389
+ String(m.maxMemoChainDepth).padStart(6),
22390
+ String(m.totalSubscriptions).padStart(5),
22391
+ String(m.batchCandidateCount).padStart(6),
22392
+ ` ${findingStr}`
22393
+ ].join(" ");
22394
+ lines.push(row);
22166
22395
  }
22167
- return null;
22168
- }
22169
- function evalStringArrayJoin(source) {
22170
- const sf = ts21.createSourceFile("__join.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
22171
- const stmt = sf.statements[0];
22172
- if (!stmt || !ts21.isVariableStatement(stmt))
22173
- return null;
22174
- let node = stmt.declarationList.declarations[0]?.initializer;
22175
- while (node && ts21.isParenthesizedExpression(node))
22176
- node = node.expression;
22177
- if (!node || !ts21.isCallExpression(node))
22178
- return null;
22179
- const callee = node.expression;
22180
- if (!ts21.isPropertyAccessExpression(callee))
22181
- return null;
22182
- if (callee.name.text !== "join")
22183
- return null;
22184
- let recv = callee.expression;
22185
- while (ts21.isParenthesizedExpression(recv))
22186
- recv = recv.expression;
22187
- if (!ts21.isArrayLiteralExpression(recv))
22188
- return null;
22189
- const parts = [];
22190
- for (const el of recv.elements) {
22191
- if (ts21.isStringLiteral(el) || ts21.isNoSubstitutionTemplateLiteral(el)) {
22192
- parts.push(el.text);
22193
- } else {
22194
- return null;
22396
+ const allFindings = sorted.flatMap((p) => p.findings.map((f) => ({ component: p.metrics.componentName, finding: f })));
22397
+ if (allFindings.length > 0) {
22398
+ lines.push("");
22399
+ lines.push("Findings:");
22400
+ for (const { component, finding } of allFindings) {
22401
+ const icon = finding.severity === "warning" ? "⚠" : "→";
22402
+ lines.push(` ${icon} ${component}: ${finding.message}`);
22403
+ lines.push(` fix: ${finding.suggestion}`);
22404
+ if (finding.loc) {
22405
+ const file = finding.loc.file.split("/").pop() ?? finding.loc.file;
22406
+ lines.push(` at ${file}:${finding.loc.line}`);
22407
+ }
22195
22408
  }
22409
+ } else {
22410
+ lines.push("");
22411
+ lines.push("No findings across all components.");
22196
22412
  }
22197
- let sep2 = ",";
22198
- if (node.arguments.length >= 1) {
22199
- const arg = node.arguments[0];
22200
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
22201
- sep2 = arg.text;
22202
- else
22203
- return null;
22204
- }
22205
- return parts.join(sep2);
22413
+ return lines.join(`
22414
+ `);
22206
22415
  }
22207
- function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
22208
- if (!ts21.isElementAccessExpression(val))
22209
- return null;
22210
- const obj = val.expression;
22211
- const arg = val.argumentExpression;
22212
- if (!ts21.isIdentifier(obj) || !ts21.isIdentifier(arg))
22213
- return null;
22214
- let indexPropName;
22215
- let defaultKey;
22216
- const resolved = resolveKey?.(arg.text);
22217
- if (resolved) {
22218
- indexPropName = resolved.propName;
22219
- defaultKey = resolved.defaultLiteral;
22220
- } else if (propsParams.some((p) => p.name === arg.text)) {
22221
- indexPropName = arg.text;
22222
- } else {
22223
- return null;
22416
+ function formatProfileDiff(diff) {
22417
+ const lines = [];
22418
+ lines.push(`${diff.componentName} — reactive profile diff (before → after)`);
22419
+ lines.push("");
22420
+ if (diff.regressions.length === 0 && diff.improvements.length === 0 && diff.neutral.length === 0) {
22421
+ lines.push(" No changes in reactive metrics.");
22422
+ return lines.join(`
22423
+ `);
22224
22424
  }
22225
- const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
22226
- if (constInfo?.value === undefined)
22227
- return null;
22228
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
22229
- if (sf.statements.length !== 1)
22230
- return null;
22231
- const stmt = sf.statements[0];
22232
- if (!ts21.isExpressionStatement(stmt))
22233
- return null;
22234
- let parsed = stmt.expression;
22235
- while (ts21.isParenthesizedExpression(parsed))
22236
- parsed = parsed.expression;
22237
- if (!ts21.isObjectLiteralExpression(parsed))
22238
- return null;
22239
- const entries = [];
22240
- for (const prop of parsed.properties) {
22241
- if (!ts21.isPropertyAssignment(prop))
22242
- return null;
22243
- let key;
22244
- if (ts21.isIdentifier(prop.name)) {
22245
- key = prop.name.text;
22246
- } else if (ts21.isStringLiteral(prop.name) || ts21.isNoSubstitutionTemplateLiteral(prop.name)) {
22247
- key = prop.name.text;
22248
- } else {
22249
- return null;
22425
+ if (diff.regressions.length > 0) {
22426
+ lines.push(" Regressions (reactive cost increased):");
22427
+ for (const e of diff.regressions) {
22428
+ lines.push(` ${e.metric}: ${e.before} → ${e.after} (+${e.delta})`);
22250
22429
  }
22251
- let v = prop.initializer;
22252
- while (ts21.isParenthesizedExpression(v))
22253
- v = v.expression;
22254
- if (ts21.isNumericLiteral(v)) {
22255
- entries.push({ key, value: { kind: "number", text: v.text } });
22256
- } else if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
22257
- entries.push({ key, value: { kind: "string", text: v.text } });
22258
- } else {
22259
- return null;
22430
+ }
22431
+ if (diff.improvements.length > 0) {
22432
+ lines.push(" Improvements (reactive cost decreased):");
22433
+ for (const e of diff.improvements) {
22434
+ lines.push(` ${e.metric}: ${e.before} ${e.after} (${e.delta})`);
22260
22435
  }
22261
22436
  }
22262
- return { indexPropName, entries, defaultKey };
22437
+ if (diff.neutral.length > 0) {
22438
+ lines.push(" Structural changes (count changes, no clear direction):");
22439
+ for (const e of diff.neutral) {
22440
+ const sign = (e.delta ?? 0) > 0 ? "+" : "";
22441
+ lines.push(` ${e.metric}: ${e.before} → ${e.after} (${sign}${e.delta})`);
22442
+ }
22443
+ }
22444
+ return lines.join(`
22445
+ `);
22446
+ }
22447
+ function profileToJSON(profile) {
22448
+ return {
22449
+ metrics: profile.metrics,
22450
+ findings: profile.findings
22451
+ };
22263
22452
  }
22264
22453
 
22265
22454
  // src/index.ts
@@ -22292,6 +22481,7 @@ export {
22292
22481
  parseBlockBodyTolerant,
22293
22482
  parseBlockBody,
22294
22483
  needsTypeBasedDetection,
22484
+ materializeGetterCalls,
22295
22485
  matchSearchParamsMethodCall,
22296
22486
  matchQueryHrefCall,
22297
22487
  matchLoweringCall,
@@ -22315,6 +22505,7 @@ export {
22315
22505
  generateClientJsWithSourceMap,
22316
22506
  generateClientJs,
22317
22507
  freeVarsInBody,
22508
+ freeIdentifiers,
22318
22509
  formatWhyUpdate,
22319
22510
  formatWastedReReruns,
22320
22511
  formatUpdatePath,
@@ -22343,6 +22534,8 @@ export {
22343
22534
  exprToString,
22344
22535
  evaluateProfileGates,
22345
22536
  evalStringArrayJoin,
22537
+ envSignalReaderFor,
22538
+ envSignalLocalNames,
22346
22539
  enableCompilerInstrumentation,
22347
22540
  emitParsedExpr,
22348
22541
  emitIRNode,
@@ -22355,6 +22548,7 @@ export {
22355
22548
  createProgramForCorpus,
22356
22549
  createError,
22357
22550
  containsHigherOrder,
22551
+ computeSsrSeedPlan,
22358
22552
  compileJSX,
22359
22553
  combineParentChildClientJs,
22360
22554
  collectModuleStringConsts,
@@ -22390,6 +22584,7 @@ export {
22390
22584
  PROFILE_SCHEMA_VERSION,
22391
22585
  JsxAdapter,
22392
22586
  ErrorCodes,
22587
+ ENV_SIGNAL_READERS,
22393
22588
  CALLBACK_METHODS,
22394
22589
  BaseAdapter,
22395
22590
  BUILTIN_LOWERING_PLUGINS,