@barefootjs/jinja 0.31.1 → 0.31.3

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
@@ -189275,10 +189275,7 @@ var conformancePins = {
189275
189275
  "date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2356" }]
189276
189276
  };
189277
189277
  // src/render-divergences.ts
189278
- var renderDivergences = {
189279
- "aliased-destructured-prop": "aliased destructured prop `{ n: count }` loses its rename — template vars, ssr-defaults, and the props bridge all key off the local name, so the prop is always undefined (https://github.com/piconic-ai/barefootjs/issues/2524)",
189280
- "composite-row-child-aliased-prop": "same defect as `aliased-destructured-prop` (#2524), inside a keyed `.map()` loop row: the nested child's renamed prop (`{ n: count }`) is always undefined, so both rows render an empty count (https://github.com/piconic-ai/barefootjs/issues/2524)"
189281
- };
189278
+ var renderDivergences = {};
189282
189279
  export {
189283
189280
  renderDivergences,
189284
189281
  jinjaAdapter,
@@ -1 +1 @@
1
- {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,eAAO,MAAM,iBAAiB,EAAE,iBAsB/B,CAAA"}
1
+ {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,eAAO,MAAM,iBAAiB,EAAE,iBAM/B,CAAA"}
package/dist/vite.js CHANGED
@@ -4,8 +4,11 @@ import { dirname, resolve } from "node:path";
4
4
  import { barefoot as coreBarefoot } from "@barefootjs/vite";
5
5
  import { devModuleUrl, loadManifest, resolveDevOrigin, resolveScriptAssets, toPosixRelative } from "@barefootjs/vite";
6
6
 
7
+ // ../jsx/src/compiler.ts
8
+ import ts23 from "typescript";
9
+
7
10
  // ../jsx/src/analyzer.ts
8
- import ts8 from "typescript";
11
+ import ts9 from "typescript";
9
12
 
10
13
  // ../jsx/src/expression-parser.ts
11
14
  import ts from "typescript";
@@ -1974,6 +1977,29 @@ import ts5 from "typescript";
1974
1977
  // ../jsx/src/ir-to-client-js/utils.ts
1975
1978
  import ts3 from "typescript";
1976
1979
 
1980
+ // ../jsx/src/template-parts.ts
1981
+ function lookupPartToJsExpr(part, opts) {
1982
+ const key = opts?.useTemplate && part.templateKey ? part.templateKey : part.key;
1983
+ const obj = "{" + Object.entries(part.cases).map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`).join(", ") + "}";
1984
+ const typed = opts?.typed ? " as Record<string, string>" : "";
1985
+ return `(${obj}${typed})[${key}]`;
1986
+ }
1987
+ function templatePartsToJsExpr(parts, opts) {
1988
+ let result = "`";
1989
+ for (const part of parts) {
1990
+ if (part.type === "string") {
1991
+ result += opts?.useTemplate && part.templateValue ? part.templateValue : part.value;
1992
+ } else if (part.type === "ternary") {
1993
+ const cond = opts?.useTemplate && part.templateCondition ? part.templateCondition : part.condition;
1994
+ result += `\${${cond} ? '${part.whenTrue}' : '${part.whenFalse}'}`;
1995
+ } else if (part.type === "lookup") {
1996
+ result += `\${${lookupPartToJsExpr(part, opts)}}`;
1997
+ }
1998
+ }
1999
+ result += "`";
2000
+ return result;
2001
+ }
2002
+
1977
2003
  // ../jsx/src/scanner/js-scanner.ts
1978
2004
  import ts2 from "typescript";
1979
2005
 
@@ -2075,6 +2101,16 @@ function escapeHtml(text) {
2075
2101
  }
2076
2102
  // ../jsx/src/ir-to-client-js/csr-substitute.ts
2077
2103
  import ts4 from "typescript";
2104
+ function extractFreeIdentifiersFromText(text) {
2105
+ if (!text || text.trim().length === 0)
2106
+ return new Set;
2107
+ const sf = ts4.createSourceFile("__free_ids__.ts", `(${text});`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2108
+ const stmt = sf.statements[0];
2109
+ if (!stmt || !ts4.isExpressionStatement(stmt))
2110
+ return new Set;
2111
+ const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2112
+ return extractFreeIdentifiersFromNode(expr);
2113
+ }
2078
2114
 
2079
2115
  // ../jsx/src/adapters/child-scope.ts
2080
2116
  function derivesScopeFromSlot(comp) {
@@ -2125,6 +2161,27 @@ var SKELETON_PATH_FORCE_CLOSE_GROUPS = [
2125
2161
  new Set(["li"])
2126
2162
  ];
2127
2163
 
2164
+ // ../jsx/src/props-binding.ts
2165
+ import ts6 from "typescript";
2166
+ function isIdentifierName(key) {
2167
+ if (key.length === 0)
2168
+ return false;
2169
+ for (let i = 0;i < key.length; ) {
2170
+ const cp = key.codePointAt(i);
2171
+ const ok = i === 0 ? ts6.isIdentifierStart(cp, ts6.ScriptTarget.Latest) : ts6.isIdentifierPart(cp, ts6.ScriptTarget.Latest);
2172
+ if (!ok)
2173
+ return false;
2174
+ i += cp > 65535 ? 2 : 1;
2175
+ }
2176
+ return true;
2177
+ }
2178
+ function propsDestructureBinding(p) {
2179
+ const callerKey = p.sourceName ?? p.name;
2180
+ const localName = p.name;
2181
+ const binding = callerKey === localName ? localName : `${isIdentifierName(callerKey) ? callerKey : JSON.stringify(callerKey)}: ${localName}`;
2182
+ return p.defaultValue ? `${binding} = ${p.defaultValue}` : binding;
2183
+ }
2184
+
2128
2185
  // ../jsx/src/instrumentation.ts
2129
2186
  var _counters = freshCounters();
2130
2187
  function freshCounters() {
@@ -2138,14 +2195,14 @@ function freshCounters() {
2138
2195
  }
2139
2196
 
2140
2197
  // ../jsx/src/analyzer-context.ts
2141
- import ts7 from "typescript";
2198
+ import ts8 from "typescript";
2142
2199
 
2143
2200
  // ../jsx/src/strip-types.ts
2144
- import ts6 from "typescript";
2201
+ import ts7 from "typescript";
2145
2202
 
2146
2203
  // ../jsx/src/analyzer-context.ts
2147
- var _typePrinter = ts7.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2148
- var _blankTypeSourceFile = ts7.createSourceFile("__bf_types__.ts", "", ts7.ScriptTarget.Latest);
2204
+ var _typePrinter = ts8.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2205
+ var _blankTypeSourceFile = ts8.createSourceFile("__bf_types__.ts", "", ts8.ScriptTarget.Latest);
2149
2206
 
2150
2207
  // ../jsx/src/errors.ts
2151
2208
  var ErrorCodes = {
@@ -2161,6 +2218,7 @@ var ErrorCodes = {
2161
2218
  JSX_IN_LOCAL_FUNCTION: "BF045",
2162
2219
  COMPONENT_REQUIRED_PROP_MISSING: "BF046",
2163
2220
  JSX_BRANCH_LOCAL_IN_CALLBACK: "BF047",
2221
+ SIBLING_COMPONENT_NOT_COMPILED: "BF048",
2164
2222
  SHARED_PROGRAM_REQUIRED: "BF050",
2165
2223
  WRONG_PACKAGE_IMPORT: "BF051",
2166
2224
  BUILTIN_REQUIRES_IMPORT: "BF054",
@@ -2190,6 +2248,7 @@ var errorMessages = {
2190
2248
  [ErrorCodes.JSX_IN_LOCAL_FUNCTION]: "Local function returns JSX but cannot be inlined. Extract it as a top-level PascalCase component or use a single return statement.",
2191
2249
  [ErrorCodes.COMPONENT_REQUIRED_PROP_MISSING]: "Built-in component is missing a required prop.",
2192
2250
  [ErrorCodes.JSX_BRANCH_LOCAL_IN_CALLBACK]: "JSX-typed local declared inside an `if`-block cannot be referenced from a callback body (ref / event handler). " + "Render it as a child instead: `<div ref={...}>{local}</div>`.",
2251
+ [ErrorCodes.SIBLING_COMPONENT_NOT_COMPILED]: "Referenced component did not compile to a template, so this reference would throw " + "`ReferenceError` at render time. Multi-return JSX dispatch (a `switch` or `if`/`else` " + "chain across multiple JSX-returning branches) cannot compile as a component in a " + `'use client' file. Extract it to a separate non-"use client" file (where it is preserved ` + "verbatim, see #932), or rewrite it as a single-return ternary/conditional chain so the " + "component pipeline can compile it.",
2193
2252
  [ErrorCodes.SHARED_PROGRAM_REQUIRED]: "Shared ts.Program required for type-based reactivity classification. This source imports a Reactive<T>-branded library (e.g. @barefootjs/form) whose getters cannot be classified by regex alone. Pass `options.program` (built via `createProgramForCorpus`) so the analyzer can resolve the brand through the TypeChecker.",
2194
2253
  [ErrorCodes.WRONG_PACKAGE_IMPORT]: "Import from wrong package.",
2195
2254
  [ErrorCodes.BUILTIN_REQUIRES_IMPORT]: "Built-in <Async> / <Region> must be imported from '@barefootjs/client'. " + "The compiler recognises these tags by their import (not by tag name), " + "so an unimported tag with this name is treated as an undeclared component.",
@@ -2363,6 +2422,54 @@ var CLIENT_EXPORTS = new Set([
2363
2422
  "Async",
2364
2423
  "Region"
2365
2424
  ]);
2425
+ function extractFreeIdentifiersFromNode(node) {
2426
+ const ids = new Set;
2427
+ const boundNames = new Set;
2428
+ function addBindingNames(name, out) {
2429
+ if (ts9.isIdentifier(name))
2430
+ out.push(name.text);
2431
+ else if (ts9.isObjectBindingPattern(name))
2432
+ name.elements.forEach((e) => addBindingNames(e.name, out));
2433
+ else if (ts9.isArrayBindingPattern(name))
2434
+ name.elements.forEach((e) => {
2435
+ if (!ts9.isOmittedExpression(e))
2436
+ addBindingNames(e.name, out);
2437
+ });
2438
+ }
2439
+ function visit(n) {
2440
+ if (ts9.isTypeNode(n))
2441
+ return;
2442
+ if (ts9.isIdentifier(n)) {
2443
+ const parent = n.parent;
2444
+ if (parent && ts9.isPropertyAccessExpression(parent) && parent.name === n)
2445
+ return;
2446
+ if (parent && ts9.isPropertyAssignment(parent) && parent.name === n)
2447
+ return;
2448
+ if (parent && ts9.isParameter(parent) && parent.name === n)
2449
+ return;
2450
+ if (parent && ts9.isVariableDeclaration(parent) && parent.name === n)
2451
+ return;
2452
+ if (boundNames.has(n.text))
2453
+ return;
2454
+ ids.add(n.text);
2455
+ return;
2456
+ }
2457
+ if (ts9.isArrowFunction(n)) {
2458
+ const params = [];
2459
+ for (const p of n.parameters)
2460
+ addBindingNames(p.name, params);
2461
+ for (const name of params)
2462
+ boundNames.add(name);
2463
+ ts9.forEachChild(n, visit);
2464
+ for (const name of params)
2465
+ boundNames.delete(name);
2466
+ return;
2467
+ }
2468
+ ts9.forEachChild(n, visit);
2469
+ }
2470
+ visit(node);
2471
+ return ids;
2472
+ }
2366
2473
  var BROWSER_ONLY_CLIENT_APIS = new Set([
2367
2474
  "useContext",
2368
2475
  "provideContext",
@@ -2381,7 +2488,7 @@ var REACTIVE_PRIMITIVES = new Set([
2381
2488
  ]);
2382
2489
 
2383
2490
  // ../jsx/src/jsx-to-ir.ts
2384
- import ts11 from "typescript";
2491
+ import ts12 from "typescript";
2385
2492
 
2386
2493
  // ../jsx/src/types.ts
2387
2494
  var SCOPE_FORBIDDEN = {
@@ -2463,10 +2570,10 @@ function findReachableNames(primaryRefs, declarations) {
2463
2570
  }
2464
2571
 
2465
2572
  // ../jsx/src/reactivity-checker.ts
2466
- import ts9 from "typescript";
2573
+ import ts10 from "typescript";
2467
2574
 
2468
2575
  // ../jsx/src/free-refs.ts
2469
- import ts10 from "typescript";
2576
+ import ts11 from "typescript";
2470
2577
  var _bindingMapCache = new WeakMap;
2471
2578
 
2472
2579
  // ../jsx/src/to-locale-date-lowering.ts
@@ -2784,6 +2891,83 @@ var toLocaleDatePlugin = {
2784
2891
  }
2785
2892
  };
2786
2893
 
2894
+ // ../jsx/src/scope/binding-scope.ts
2895
+ class BindingScope {
2896
+ frames;
2897
+ static EMPTY = new BindingScope([]);
2898
+ constructor(frames) {
2899
+ this.frames = frames;
2900
+ }
2901
+ enterLoopRow(loop) {
2902
+ const bindings = new Map;
2903
+ if (loop.paramBindings && loop.paramBindings.length > 0) {
2904
+ for (const b of loop.paramBindings)
2905
+ bindings.set(b.name, { source: "destructure" });
2906
+ } else {
2907
+ bindings.set(loop.param, { source: "item" });
2908
+ }
2909
+ if (loop.index != null)
2910
+ bindings.set(loop.index, { source: "index" });
2911
+ for (const name of loop.preamble?.declaredNames ?? [])
2912
+ bindings.set(name, { source: "preamble" });
2913
+ const frame = { kind: "loop-row", bindings };
2914
+ return new BindingScope([frame, ...this.frames]);
2915
+ }
2916
+ enterCallback(params) {
2917
+ const bindings = new Map;
2918
+ for (const name of params)
2919
+ bindings.set(name, { source: "param" });
2920
+ const frame = { kind: "callback", bindings };
2921
+ return new BindingScope([frame, ...this.frames]);
2922
+ }
2923
+ isBound(name) {
2924
+ for (const frame of this.frames) {
2925
+ if (frame.bindings.has(name))
2926
+ return true;
2927
+ }
2928
+ return false;
2929
+ }
2930
+ lookup(name) {
2931
+ for (let depth = 0;depth < this.frames.length; depth++) {
2932
+ const frame = this.frames[depth];
2933
+ const binding = frame.bindings.get(name);
2934
+ if (binding)
2935
+ return { depth, frame, binding };
2936
+ }
2937
+ return null;
2938
+ }
2939
+ boundNames() {
2940
+ if (this.boundNamesCache)
2941
+ return this.boundNamesCache;
2942
+ const names = new Set;
2943
+ for (const frame of this.frames) {
2944
+ for (const name of frame.bindings.keys())
2945
+ names.add(name);
2946
+ }
2947
+ this.boundNamesCache = names;
2948
+ return names;
2949
+ }
2950
+ boundNamesCache;
2951
+ valueBoundNamesCache;
2952
+ valueBoundNames() {
2953
+ if (this.valueBoundNamesCache)
2954
+ return this.valueBoundNamesCache;
2955
+ const names = new Set;
2956
+ for (const frame of this.frames) {
2957
+ for (const [name, binding] of frame.bindings) {
2958
+ if (binding.source === "item" || binding.source === "index" || binding.source === "destructure") {
2959
+ names.add(name);
2960
+ }
2961
+ }
2962
+ }
2963
+ this.valueBoundNamesCache = names;
2964
+ return names;
2965
+ }
2966
+ asShadowPredicate() {
2967
+ return (name) => this.isBound(name);
2968
+ }
2969
+ }
2970
+
2787
2971
  // ../jsx/src/jsx-to-ir.ts
2788
2972
  var EMPTY_BOUND = new Set;
2789
2973
  var constInitializerCache = new WeakMap;
@@ -2880,13 +3064,13 @@ var KEYWORDS_AND_GLOBALS = new Set([
2880
3064
  ]);
2881
3065
 
2882
3066
  // ../jsx/src/ir-to-client-js/walk-prop-accesses.ts
2883
- import ts12 from "typescript";
3067
+ import ts13 from "typescript";
2884
3068
 
2885
3069
  // ../jsx/src/value-references.ts
2886
- import ts13 from "typescript";
3070
+ import ts14 from "typescript";
2887
3071
 
2888
3072
  // ../jsx/src/relocate.ts
2889
- import ts14 from "typescript";
3073
+ import ts15 from "typescript";
2890
3074
 
2891
3075
  // ../jsx/src/lowering-registry.ts
2892
3076
  var plugins = [];
@@ -3103,10 +3287,10 @@ function matchSearchParamsMethodCall(callee, args, localNames) {
3103
3287
  }
3104
3288
 
3105
3289
  // ../jsx/src/ir-to-client-js/prune-unused-prop-extractions.ts
3106
- import ts15 from "typescript";
3290
+ import ts16 from "typescript";
3107
3291
 
3108
3292
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
3109
- import ts16 from "typescript";
3293
+ import ts17 from "typescript";
3110
3294
  var NO_PREAMBLE = {
3111
3295
  lazySafe: true,
3112
3296
  facts: { declaredNames: new Set, freeNames: new Set }
@@ -3156,7 +3340,7 @@ var INERT_BINDING_GLOBALS = new Set([
3156
3340
  ]);
3157
3341
 
3158
3342
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
3159
- import ts17 from "typescript";
3343
+ import ts18 from "typescript";
3160
3344
 
3161
3345
  // ../jsx/src/ir-to-client-js/control-flow/stringify/event-delegation.ts
3162
3346
  var NON_BUBBLING_EVENTS = new Set([
@@ -3171,7 +3355,7 @@ var NON_BUBBLING_EVENTS = new Set([
3171
3355
  ]);
3172
3356
 
3173
3357
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
3174
- import ts18 from "typescript";
3358
+ import ts19 from "typescript";
3175
3359
 
3176
3360
  // ../jsx/src/ir-to-client-js/source-map.ts
3177
3361
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -3262,15 +3446,15 @@ class SourceMapGenerator {
3262
3446
  }
3263
3447
 
3264
3448
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
3265
- import ts19 from "typescript";
3449
+ import ts20 from "typescript";
3266
3450
 
3267
3451
  // ../jsx/src/ssr-defaults.ts
3268
- import ts20 from "typescript";
3452
+ import ts21 from "typescript";
3269
3453
  var UNRESOLVED = Symbol("unresolved");
3270
3454
  var NO_RETURN = Symbol("no-return");
3271
3455
 
3272
3456
  // ../jsx/src/augment-inherited-props.ts
3273
- import ts21 from "typescript";
3457
+ import ts22 from "typescript";
3274
3458
  function collectContextConsumers(metadata) {
3275
3459
  const constants = metadata.localConstants ?? [];
3276
3460
  const contextDefaults = new Map;
@@ -3302,47 +3486,47 @@ function collectContextConsumers(metadata) {
3302
3486
  }
3303
3487
  function parseUseContextArg(source) {
3304
3488
  const expr = parseSingleExpression(source);
3305
- if (!expr || !ts21.isCallExpression(expr))
3489
+ if (!expr || !ts22.isCallExpression(expr))
3306
3490
  return null;
3307
- if (!ts21.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3491
+ if (!ts22.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3308
3492
  return null;
3309
3493
  if (expr.arguments.length !== 1)
3310
3494
  return null;
3311
3495
  const arg = expr.arguments[0];
3312
- return ts21.isIdentifier(arg) ? arg.text : null;
3496
+ return ts22.isIdentifier(arg) ? arg.text : null;
3313
3497
  }
3314
3498
  function parseCreateContextDefault(source) {
3315
3499
  const expr = parseSingleExpression(source);
3316
- if (!expr || !ts21.isCallExpression(expr))
3500
+ if (!expr || !ts22.isCallExpression(expr))
3317
3501
  return null;
3318
3502
  if (expr.arguments.length === 0)
3319
3503
  return null;
3320
3504
  const arg = expr.arguments[0];
3321
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3505
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3322
3506
  return arg.text;
3323
- if (ts21.isNumericLiteral(arg))
3507
+ if (ts22.isNumericLiteral(arg))
3324
3508
  return Number(arg.text);
3325
- if (arg.kind === ts21.SyntaxKind.TrueKeyword)
3509
+ if (arg.kind === ts22.SyntaxKind.TrueKeyword)
3326
3510
  return true;
3327
- if (arg.kind === ts21.SyntaxKind.FalseKeyword)
3511
+ if (arg.kind === ts22.SyntaxKind.FalseKeyword)
3328
3512
  return false;
3329
3513
  return null;
3330
3514
  }
3331
3515
  function isObjectLiteralCreateContextDefault(source) {
3332
3516
  const expr = parseSingleExpression(source);
3333
- if (!expr || !ts21.isCallExpression(expr))
3517
+ if (!expr || !ts22.isCallExpression(expr))
3334
3518
  return false;
3335
3519
  if (expr.arguments.length === 0)
3336
3520
  return false;
3337
- return ts21.isObjectLiteralExpression(expr.arguments[0]);
3521
+ return ts22.isObjectLiteralExpression(expr.arguments[0]);
3338
3522
  }
3339
3523
  function parseSingleExpression(source) {
3340
- const sf = ts21.createSourceFile("__ctx.ts", `(${source})`, ts21.ScriptTarget.Latest, false);
3524
+ const sf = ts22.createSourceFile("__ctx.ts", `(${source})`, ts22.ScriptTarget.Latest, false);
3341
3525
  const stmt = sf.statements[0];
3342
- if (!stmt || !ts21.isExpressionStatement(stmt))
3526
+ if (!stmt || !ts22.isExpressionStatement(stmt))
3343
3527
  return null;
3344
3528
  let e = stmt.expression;
3345
- while (ts21.isParenthesizedExpression(e))
3529
+ while (ts22.isParenthesizedExpression(e))
3346
3530
  e = e.expression;
3347
3531
  return e;
3348
3532
  }
@@ -3367,25 +3551,25 @@ function augmentInheritedPropAccesses(ir) {
3367
3551
  const pinCoalesceLiterals = (s) => {
3368
3552
  if (!s || !s.includes(propsObj))
3369
3553
  return;
3370
- const sf = ts21.createSourceFile("__aug.ts", `(${s})`, ts21.ScriptTarget.Latest, false);
3554
+ const sf = ts22.createSourceFile("__aug.ts", `(${s})`, ts22.ScriptTarget.Latest, false);
3371
3555
  const visit = (n) => {
3372
- if (ts21.isBinaryExpression(n) && (n.operatorToken.kind === ts21.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts21.SyntaxKind.BarBarToken)) {
3556
+ if (ts22.isBinaryExpression(n) && (n.operatorToken.kind === ts22.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts22.SyntaxKind.BarBarToken)) {
3373
3557
  let left = n.left;
3374
- while (ts21.isParenthesizedExpression(left))
3558
+ while (ts22.isParenthesizedExpression(left))
3375
3559
  left = left.expression;
3376
- if (ts21.isPropertyAccessExpression(left) && ts21.isIdentifier(left.expression) && left.expression.text === propsObj) {
3560
+ if (ts22.isPropertyAccessExpression(left) && ts22.isIdentifier(left.expression) && left.expression.text === propsObj) {
3377
3561
  const name = left.name.text;
3378
3562
  let right = n.right;
3379
- while (ts21.isParenthesizedExpression(right))
3563
+ while (ts22.isParenthesizedExpression(right))
3380
3564
  right = right.expression;
3381
- if (ts21.isPrefixUnaryExpression(right))
3565
+ if (ts22.isPrefixUnaryExpression(right))
3382
3566
  right = right.operand;
3383
- const kind = ts21.isNumericLiteral(right) ? "number" : right.kind === ts21.SyntaxKind.TrueKeyword || right.kind === ts21.SyntaxKind.FalseKeyword ? "boolean" : ts21.isStringLiteralLike(right) ? "string" : null;
3567
+ const kind = ts22.isNumericLiteral(right) ? "number" : right.kind === ts22.SyntaxKind.TrueKeyword || right.kind === ts22.SyntaxKind.FalseKeyword ? "boolean" : ts22.isStringLiteralLike(right) ? "string" : null;
3384
3568
  if (kind && !coalesceLiteralTypes.has(name))
3385
3569
  coalesceLiteralTypes.set(name, kind);
3386
3570
  }
3387
3571
  }
3388
- ts21.forEachChild(n, visit);
3572
+ ts22.forEachChild(n, visit);
3389
3573
  };
3390
3574
  visit(sf);
3391
3575
  };
@@ -3496,33 +3680,33 @@ function augmentInheritedPropAccesses(ir) {
3496
3680
  }
3497
3681
  }
3498
3682
  function parseStaticStringConst(source) {
3499
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3683
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3500
3684
  const stmt = sf.statements[0];
3501
- if (!stmt || !ts21.isVariableStatement(stmt))
3685
+ if (!stmt || !ts22.isVariableStatement(stmt))
3502
3686
  return null;
3503
3687
  let init = stmt.declarationList.declarations[0]?.initializer;
3504
- while (init && ts21.isParenthesizedExpression(init))
3688
+ while (init && ts22.isParenthesizedExpression(init))
3505
3689
  init = init.expression;
3506
3690
  if (!init)
3507
3691
  return null;
3508
- if (ts21.isStringLiteral(init) || ts21.isNoSubstitutionTemplateLiteral(init)) {
3692
+ if (ts22.isStringLiteral(init) || ts22.isNoSubstitutionTemplateLiteral(init)) {
3509
3693
  return init.text;
3510
3694
  }
3511
3695
  return evalStringArrayJoin(source);
3512
3696
  }
3513
3697
  function evalTemplateOfStringConsts(source, resolved) {
3514
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3698
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3515
3699
  const stmt = sf.statements[0];
3516
- if (!stmt || !ts21.isVariableStatement(stmt))
3700
+ if (!stmt || !ts22.isVariableStatement(stmt))
3517
3701
  return null;
3518
3702
  let init = stmt.declarationList.declarations[0]?.initializer;
3519
- while (init && ts21.isParenthesizedExpression(init))
3703
+ while (init && ts22.isParenthesizedExpression(init))
3520
3704
  init = init.expression;
3521
- if (!init || !ts21.isTemplateExpression(init))
3705
+ if (!init || !ts22.isTemplateExpression(init))
3522
3706
  return null;
3523
3707
  let out = init.head.text;
3524
3708
  for (const span of init.templateSpans) {
3525
- if (!ts21.isIdentifier(span.expression))
3709
+ if (!ts22.isIdentifier(span.expression))
3526
3710
  return null;
3527
3711
  const value = resolved.get(span.expression.text);
3528
3712
  if (value === undefined)
@@ -3553,30 +3737,30 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3553
3737
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
3554
3738
  if (constInfo?.value === undefined)
3555
3739
  return null;
3556
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3740
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3557
3741
  if (sf.statements.length !== 1)
3558
3742
  return null;
3559
3743
  const stmt = sf.statements[0];
3560
- if (!ts21.isExpressionStatement(stmt))
3744
+ if (!ts22.isExpressionStatement(stmt))
3561
3745
  return null;
3562
3746
  let parsed = stmt.expression;
3563
- while (ts21.isParenthesizedExpression(parsed))
3747
+ while (ts22.isParenthesizedExpression(parsed))
3564
3748
  parsed = parsed.expression;
3565
- if (!ts21.isObjectLiteralExpression(parsed))
3749
+ if (!ts22.isObjectLiteralExpression(parsed))
3566
3750
  return null;
3567
3751
  for (const prop of parsed.properties) {
3568
- if (!ts21.isPropertyAssignment(prop))
3752
+ if (!ts22.isPropertyAssignment(prop))
3569
3753
  continue;
3570
3754
  const name = prop.name;
3571
- const propKey = ts21.isIdentifier(name) || ts21.isStringLiteral(name) || ts21.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3755
+ const propKey = ts22.isIdentifier(name) || ts22.isStringLiteral(name) || ts22.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3572
3756
  if (propKey !== key)
3573
3757
  continue;
3574
3758
  let v = prop.initializer;
3575
- while (ts21.isParenthesizedExpression(v))
3759
+ while (ts22.isParenthesizedExpression(v))
3576
3760
  v = v.expression;
3577
- if (ts21.isNumericLiteral(v))
3761
+ if (ts22.isNumericLiteral(v))
3578
3762
  return { kind: "number", text: v.text };
3579
- if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3763
+ if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3580
3764
  return { kind: "string", text: v.text };
3581
3765
  }
3582
3766
  return null;
@@ -3584,28 +3768,28 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3584
3768
  return null;
3585
3769
  }
3586
3770
  function evalStringArrayJoin(source) {
3587
- const sf = ts21.createSourceFile("__join.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3771
+ const sf = ts22.createSourceFile("__join.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3588
3772
  const stmt = sf.statements[0];
3589
- if (!stmt || !ts21.isVariableStatement(stmt))
3773
+ if (!stmt || !ts22.isVariableStatement(stmt))
3590
3774
  return null;
3591
3775
  let node = stmt.declarationList.declarations[0]?.initializer;
3592
- while (node && ts21.isParenthesizedExpression(node))
3776
+ while (node && ts22.isParenthesizedExpression(node))
3593
3777
  node = node.expression;
3594
- if (!node || !ts21.isCallExpression(node))
3778
+ if (!node || !ts22.isCallExpression(node))
3595
3779
  return null;
3596
3780
  const callee = node.expression;
3597
- if (!ts21.isPropertyAccessExpression(callee))
3781
+ if (!ts22.isPropertyAccessExpression(callee))
3598
3782
  return null;
3599
3783
  if (callee.name.text !== "join")
3600
3784
  return null;
3601
3785
  let recv = callee.expression;
3602
- while (ts21.isParenthesizedExpression(recv))
3786
+ while (ts22.isParenthesizedExpression(recv))
3603
3787
  recv = recv.expression;
3604
- if (!ts21.isArrayLiteralExpression(recv))
3788
+ if (!ts22.isArrayLiteralExpression(recv))
3605
3789
  return null;
3606
3790
  const parts = [];
3607
3791
  for (const el of recv.elements) {
3608
- if (ts21.isStringLiteral(el) || ts21.isNoSubstitutionTemplateLiteral(el)) {
3792
+ if (ts22.isStringLiteral(el) || ts22.isNoSubstitutionTemplateLiteral(el)) {
3609
3793
  parts.push(el.text);
3610
3794
  } else {
3611
3795
  return null;
@@ -3614,7 +3798,7 @@ function evalStringArrayJoin(source) {
3614
3798
  let sep = ",";
3615
3799
  if (node.arguments.length >= 1) {
3616
3800
  const arg = node.arguments[0];
3617
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3801
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3618
3802
  sep = arg.text;
3619
3803
  else
3620
3804
  return null;
@@ -3622,11 +3806,11 @@ function evalStringArrayJoin(source) {
3622
3806
  return parts.join(sep);
3623
3807
  }
3624
3808
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3625
- if (!ts21.isElementAccessExpression(val))
3809
+ if (!ts22.isElementAccessExpression(val))
3626
3810
  return null;
3627
3811
  const obj = val.expression;
3628
3812
  const arg = val.argumentExpression;
3629
- if (!ts21.isIdentifier(obj) || !ts21.isIdentifier(arg))
3813
+ if (!ts22.isIdentifier(obj) || !ts22.isIdentifier(arg))
3630
3814
  return null;
3631
3815
  let indexPropName;
3632
3816
  let defaultKey;
@@ -3642,35 +3826,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3642
3826
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
3643
3827
  if (constInfo?.value === undefined)
3644
3828
  return null;
3645
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3829
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3646
3830
  if (sf.statements.length !== 1)
3647
3831
  return null;
3648
3832
  const stmt = sf.statements[0];
3649
- if (!ts21.isExpressionStatement(stmt))
3833
+ if (!ts22.isExpressionStatement(stmt))
3650
3834
  return null;
3651
3835
  let parsed = stmt.expression;
3652
- while (ts21.isParenthesizedExpression(parsed))
3836
+ while (ts22.isParenthesizedExpression(parsed))
3653
3837
  parsed = parsed.expression;
3654
- if (!ts21.isObjectLiteralExpression(parsed))
3838
+ if (!ts22.isObjectLiteralExpression(parsed))
3655
3839
  return null;
3656
3840
  const entries = [];
3657
3841
  for (const prop of parsed.properties) {
3658
- if (!ts21.isPropertyAssignment(prop))
3842
+ if (!ts22.isPropertyAssignment(prop))
3659
3843
  return null;
3660
3844
  let key;
3661
- if (ts21.isIdentifier(prop.name)) {
3845
+ if (ts22.isIdentifier(prop.name)) {
3662
3846
  key = prop.name.text;
3663
- } else if (ts21.isStringLiteral(prop.name) || ts21.isNoSubstitutionTemplateLiteral(prop.name)) {
3847
+ } else if (ts22.isStringLiteral(prop.name) || ts22.isNoSubstitutionTemplateLiteral(prop.name)) {
3664
3848
  key = prop.name.text;
3665
3849
  } else {
3666
3850
  return null;
3667
3851
  }
3668
3852
  let v = prop.initializer;
3669
- while (ts21.isParenthesizedExpression(v))
3853
+ while (ts22.isParenthesizedExpression(v))
3670
3854
  v = v.expression;
3671
- if (ts21.isNumericLiteral(v)) {
3855
+ if (ts22.isNumericLiteral(v)) {
3672
3856
  entries.push({ key, value: { kind: "number", text: v.text } });
3673
- } else if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3857
+ } else if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3674
3858
  entries.push({ key, value: { kind: "string", text: v.text } });
3675
3859
  } else {
3676
3860
  return null;
@@ -3726,7 +3910,7 @@ function computeSsrSeedPlan(metadata) {
3726
3910
  // ../jsx/src/rich-type-refusal.ts
3727
3911
  var EMPTY_BINDINGS2 = new Map;
3728
3912
  // ../jsx/src/shared-program.ts
3729
- import ts22 from "typescript";
3913
+ import ts24 from "typescript";
3730
3914
  // ../jsx/src/adapters/interface.ts
3731
3915
  class BaseAdapter {
3732
3916
  renderChildren(children) {
@@ -3802,7 +3986,7 @@ class JsxAdapter extends BaseAdapter {
3802
3986
  }
3803
3987
  const rawInitialValue = preserveTypes ? signal.typedInitialValue ?? signal.initialValue : signal.initialValue;
3804
3988
  const initialValue = rawInitialValue.trim().startsWith("{") ? `(${rawInitialValue})` : rawInitialValue;
3805
- const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive";
3989
+ const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive" && signal.type.raw !== "object";
3806
3990
  if (needsTypeAssertion) {
3807
3991
  lines.push(` const ${signal.getter} = () => ${initialValue} as ${signal.type.raw}`);
3808
3992
  } else {
@@ -3821,12 +4005,16 @@ class JsxAdapter extends BaseAdapter {
3821
4005
  const computation = preserveTypes ? memo.typedComputation ?? memo.computation : memo.computation;
3822
4006
  lines.push(` const ${memo.name} = ${computation}`);
3823
4007
  }
4008
+ const moduleScopeNames = this.moduleScopeDeclarationNames(ir);
3824
4009
  for (const constant of ir.metadata.localConstants) {
3825
4010
  if (constant.isExported)
3826
4011
  continue;
4012
+ if (moduleScopeNames.has(constant.name))
4013
+ continue;
3827
4014
  const keyword = constant.declarationKind ?? "const";
3828
4015
  if (!constant.value) {
3829
- lines.push(` ${keyword} ${constant.name}`);
4016
+ const typeAnnotation = preserveTypes && (constant.typeAnnotation ?? constant.type) ? `: ${constant.typeAnnotation ?? constant.type?.raw}` : "";
4017
+ lines.push(` ${keyword} ${constant.name}${typeAnnotation}`);
3830
4018
  continue;
3831
4019
  }
3832
4020
  const value = constant.value.trim();
@@ -3835,9 +4023,12 @@ class JsxAdapter extends BaseAdapter {
3835
4023
  if (!reachable.has(constant.name))
3836
4024
  continue;
3837
4025
  const constValue = preserveTypes ? constant.typedValue ?? constant.value : constant.value;
3838
- lines.push(` ${keyword} ${constant.name} = ${constValue}`);
4026
+ const letTypeAnnotation = preserveTypes && keyword === "let" && constant.typeAnnotation ? `: ${constant.typeAnnotation}` : "";
4027
+ lines.push(` ${keyword} ${constant.name}${letTypeAnnotation} = ${constValue}`);
3839
4028
  }
3840
4029
  for (const func of localFunctions) {
4030
+ if (moduleScopeNames.has(func.name))
4031
+ continue;
3841
4032
  if (!reachable.has(func.name))
3842
4033
  continue;
3843
4034
  const params = preserveTypes && func.typedParams !== undefined ? func.typedParams : func.params.map(formatParamWithType).join(", ");
@@ -3847,6 +4038,127 @@ class JsxAdapter extends BaseAdapter {
3847
4038
  lines.push(` ${asyncKw}function ${func.name}(${params})${returnAnnotation} ${body}`);
3848
4039
  }
3849
4040
  return lines.join(`
4041
+ `);
4042
+ }
4043
+ moduleScopeNamesCache = new WeakMap;
4044
+ moduleScopeDeclarationNames(ir) {
4045
+ const cached = this.moduleScopeNamesCache.get(ir);
4046
+ if (cached)
4047
+ return cached;
4048
+ const componentScope = new Set;
4049
+ for (const sig of ir.metadata.signals) {
4050
+ if (sig.isModule)
4051
+ continue;
4052
+ componentScope.add(sig.getter);
4053
+ if (sig.setter)
4054
+ componentScope.add(sig.setter);
4055
+ }
4056
+ for (const memo of ir.metadata.memos) {
4057
+ if (!memo.isModule)
4058
+ componentScope.add(memo.name);
4059
+ }
4060
+ for (const p of ir.metadata.propsParams)
4061
+ componentScope.add(p.name);
4062
+ if (ir.metadata.propsObjectName)
4063
+ componentScope.add(ir.metadata.propsObjectName);
4064
+ if (ir.metadata.restPropsName)
4065
+ componentScope.add(ir.metadata.restPropsName);
4066
+ for (const c of ir.metadata.localConstants) {
4067
+ if (!c.isModule)
4068
+ componentScope.add(c.name);
4069
+ }
4070
+ for (const f of ir.metadata.localFunctions) {
4071
+ if (!f.isModule)
4072
+ componentScope.add(f.name);
4073
+ }
4074
+ const exported = new Set;
4075
+ const candidates = new Map;
4076
+ for (const c of ir.metadata.localConstants) {
4077
+ if (!c.isModule)
4078
+ continue;
4079
+ if (c.isJsx || c.isJsxFunction)
4080
+ continue;
4081
+ if (c.isExported) {
4082
+ exported.add(c.name);
4083
+ continue;
4084
+ }
4085
+ candidates.set(c.name, c.freeIdentifiers ?? extractFreeIdentifiersFromText(c.value ?? ""));
4086
+ }
4087
+ for (const f of ir.metadata.localFunctions) {
4088
+ if (!f.isModule)
4089
+ continue;
4090
+ if (f.isJsxFunction || f.isMultiReturnJsxHelper)
4091
+ continue;
4092
+ if (f.isExported) {
4093
+ exported.add(f.name);
4094
+ continue;
4095
+ }
4096
+ const params = f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4097
+ candidates.set(f.name, extractFreeIdentifiersFromText(`(${params}) => ${f.body}`));
4098
+ }
4099
+ const referencesAny = (refs, names) => {
4100
+ for (const ref of refs) {
4101
+ if (names.has(ref))
4102
+ return true;
4103
+ }
4104
+ return false;
4105
+ };
4106
+ let changed = true;
4107
+ while (changed) {
4108
+ changed = false;
4109
+ for (const [name, refs] of candidates) {
4110
+ if (referencesAny(refs, componentScope)) {
4111
+ candidates.delete(name);
4112
+ componentScope.add(name);
4113
+ changed = true;
4114
+ }
4115
+ }
4116
+ }
4117
+ const result = new Set([...exported, ...candidates.keys()]);
4118
+ this.moduleScopeNamesCache.set(ir, result);
4119
+ return result;
4120
+ }
4121
+ generateModuleScopeDeclarations(ir) {
4122
+ const { preserveTypes } = this.jsxConfig;
4123
+ const moduleNames = this.moduleScopeDeclarationNames(ir);
4124
+ const entries = [];
4125
+ for (const t of ir.metadata.typeDefinitions) {
4126
+ entries.push({ line: t.loc.start.line, text: t.definition });
4127
+ }
4128
+ for (const c of ir.metadata.localConstants) {
4129
+ if (!c.isModule || !moduleNames.has(c.name))
4130
+ continue;
4131
+ const keyword = c.declarationKind ?? "const";
4132
+ const exportKw = c.isExported ? "export " : "";
4133
+ if (!c.value) {
4134
+ const typeAnnotation = preserveTypes && (c.typeAnnotation ?? c.type) ? `: ${c.typeAnnotation ?? c.type?.raw}` : "";
4135
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name}${typeAnnotation}` });
4136
+ continue;
4137
+ }
4138
+ const trimmed = c.value.trim();
4139
+ if (/^new WeakMap\b/.test(trimmed))
4140
+ continue;
4141
+ if (c.isExported && /^createContext\b/.test(trimmed))
4142
+ continue;
4143
+ const value = preserveTypes ? c.typedValue ?? c.value : c.value;
4144
+ const letTypeAnnotation = preserveTypes && keyword === "let" && c.typeAnnotation ? `: ${c.typeAnnotation}` : "";
4145
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name}${letTypeAnnotation} = ${value}` });
4146
+ }
4147
+ for (const f of ir.metadata.localFunctions) {
4148
+ if (!f.isModule || !moduleNames.has(f.name))
4149
+ continue;
4150
+ const params = preserveTypes && f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4151
+ const returnAnnotation = preserveTypes && f.typedReturnType ? `: ${f.typedReturnType}` : "";
4152
+ const body = preserveTypes ? f.typedBody ?? f.body : f.body;
4153
+ const asyncKw = f.isAsync ? "async " : "";
4154
+ const exportKw = f.isExported ? "export " : "";
4155
+ entries.push({
4156
+ line: f.loc.start.line,
4157
+ text: `${exportKw}${asyncKw}function ${f.name}(${params})${returnAnnotation} ${body}`
4158
+ });
4159
+ }
4160
+ entries.sort((a, b) => a.line - b.line);
4161
+ return entries.map((e) => e.text).join(`
3850
4162
  `);
3851
4163
  }
3852
4164
  renderNodeRaw(node) {
@@ -3858,6 +4170,15 @@ class JsxAdapter extends BaseAdapter {
3858
4170
  }
3859
4171
  return this.renderNode(node);
3860
4172
  }
4173
+ renderTemplatePartsAsJs(parts) {
4174
+ return templatePartsToJsExpr(parts, { typed: this.jsxConfig.preserveTypes });
4175
+ }
4176
+ expressionValueToJs(value) {
4177
+ if (this.jsxConfig.preserveTypes && value.parts && value.expr === templatePartsToJsExpr(value.parts)) {
4178
+ return this.renderTemplatePartsAsJs(value.parts);
4179
+ }
4180
+ return value.expr;
4181
+ }
3861
4182
  renderScopeMarker(instanceIdExpr) {
3862
4183
  return `${BF_SCOPE}={${instanceIdExpr}}`;
3863
4184
  }
@@ -3925,6 +4246,7 @@ class TestAdapter extends JsxAdapter {
3925
4246
  generate(ir) {
3926
4247
  this.componentName = ir.metadata.componentName;
3927
4248
  const imports = this.generateImports(ir);
4249
+ const moduleConstants = this.generateModuleScopeDeclarations(ir);
3928
4250
  const types = this.generateTypes(ir);
3929
4251
  const component = this.generateComponent(ir);
3930
4252
  const defaultExport = ir.metadata.hasDefaultExport ? `
@@ -3933,9 +4255,11 @@ export default ${this.componentName}` : "";
3933
4255
  imports,
3934
4256
  types: types || "",
3935
4257
  component,
3936
- defaultExport
4258
+ defaultExport,
4259
+ moduleConstants,
4260
+ moduleConstantsIncludeExports: true
3937
4261
  };
3938
- const template = [imports, types, component].filter(Boolean).join(`
4262
+ const template = [imports, moduleConstants, types, component].filter(Boolean).join(`
3939
4263
 
3940
4264
  `) + defaultExport;
3941
4265
  return {
@@ -3966,9 +4290,6 @@ export default ${this.componentName}` : "";
3966
4290
  }
3967
4291
  generateTypes(ir) {
3968
4292
  const lines = [];
3969
- for (const typeDef of ir.metadata.typeDefinitions) {
3970
- lines.push(typeDef.definition);
3971
- }
3972
4293
  const propsTypeName = ir.metadata.propsType?.raw;
3973
4294
  if (propsTypeName && !ir.metadata.propsObjectName) {
3974
4295
  lines.push("");
@@ -3991,7 +4312,7 @@ export default ${this.componentName}` : "";
3991
4312
  const bodyRefText = [jsxBody, signalInits, scopeIdLine].join(`
3992
4313
  `);
3993
4314
  const bfScopeAlias = /\b__bfScope\b/.test(bodyRefText) ? "__bfScope" : "__bfScope: _bfScope";
3994
- const propsParams = ir.metadata.propsParams.map((p) => p.defaultValue ? `${p.name} = ${p.defaultValue}` : p.name).join(", ");
4315
+ const propsParams = ir.metadata.propsParams.map((p) => propsDestructureBinding(p)).join(", ");
3995
4316
  const restPropsName = ir.metadata.restPropsName;
3996
4317
  const hydrationProps = `__instanceId, ${bfScopeAlias}`;
3997
4318
  const parts = [];
@@ -4138,13 +4459,7 @@ export default ${this.componentName}` : "";
4138
4459
  }
4139
4460
  flattenTemplate(value) {
4140
4461
  const v = value;
4141
- return "`" + v.parts.map((p) => {
4142
- if (p.type === "string")
4143
- return p.value;
4144
- if (p.type === "ternary")
4145
- return `\${${p.condition} ? '${p.whenTrue}' : '${p.whenFalse}'}`;
4146
- return `\${(${JSON.stringify(p.cases)})[${p.key}]}`;
4147
- }).join("") + "`";
4462
+ return this.renderTemplatePartsAsJs(v.parts);
4148
4463
  }
4149
4464
  renderComponentProps(comp) {
4150
4465
  const parts = [];
@@ -4618,7 +4933,7 @@ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
4618
4933
  };
4619
4934
  }
4620
4935
  // ../jsx/src/combine-client-js.ts
4621
- import ts23 from "typescript";
4936
+ import ts25 from "typescript";
4622
4937
  // ../jsx/src/loop-destructure.ts
4623
4938
  function isLowerableLoopDestructure(loop) {
4624
4939
  const bindings = loop.paramBindings;
@@ -4758,9 +5073,9 @@ function escapeRe(s) {
4758
5073
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4759
5074
  }
4760
5075
  // ../jsx/src/debug.ts
4761
- import ts24 from "typescript";
5076
+ import ts26 from "typescript";
4762
5077
  // ../jsx/src/profiler.ts
4763
- import ts25 from "typescript";
5078
+ import ts27 from "typescript";
4764
5079
 
4765
5080
  // ../jsx/src/index.ts
4766
5081
  registerBuiltinLoweringPlugins();
@@ -5586,7 +5901,7 @@ function collectImportedLoopChildComponentErrors(ir, componentName) {
5586
5901
  }
5587
5902
 
5588
5903
  // src/adapter/spread/spread-codegen.ts
5589
- import ts26 from "typescript";
5904
+ import ts28 from "typescript";
5590
5905
  function conditionalSpreadToJinja(ctx, expr) {
5591
5906
  if (!expr || expr.kind !== "conditional")
5592
5907
  return null;
@@ -5641,7 +5956,7 @@ function recordIndexAccessToJinja(ctx, val) {
5641
5956
  if (val.kind !== "index-access" || val.object.kind !== "identifier" || val.index.kind !== "identifier") {
5642
5957
  return null;
5643
5958
  }
5644
- const tsVal = ts26.factory.createElementAccessExpression(ts26.factory.createIdentifier(val.object.name), ts26.factory.createIdentifier(val.index.name));
5959
+ const tsVal = ts28.factory.createElementAccessExpression(ts28.factory.createIdentifier(val.object.name), ts28.factory.createIdentifier(val.index.name));
5645
5960
  const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants ?? [], ctx.propsParams);
5646
5961
  if (!parsed)
5647
5962
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jinja",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
4
4
  "description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "directory": "packages/adapter-jinja"
54
54
  },
55
55
  "dependencies": {
56
- "@barefootjs/shared": "0.31.1"
56
+ "@barefootjs/shared": "0.31.3"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@barefootjs/jsx": ">=0.2.0",
@@ -70,9 +70,9 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@barefootjs/adapter-tests": "0.1.0",
73
- "@barefootjs/jsx": "0.31.1",
74
- "@barefootjs/vite": "0.31.1",
75
- "@barefootjs/client": "0.31.1",
73
+ "@barefootjs/jsx": "0.31.3",
74
+ "@barefootjs/vite": "0.31.3",
75
+ "@barefootjs/client": "0.31.3",
76
76
  "typescript": "^5.0.0",
77
77
  "vite": "^6.0.0"
78
78
  }
@@ -484,20 +484,35 @@ _DATE_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
484
484
  def _derive_stash_from_defaults(defaults: dict, props: dict) -> dict:
485
485
  """Derive template-stash kvs from a manifest entry's `ssrDefaults`
486
486
  section. See BarefootJS.pm's `_derive_stash_from_defaults` docstring for
487
- the full field-shape contract."""
487
+ the full field-shape contract.
488
+
489
+ `props` arrives ALREADY keyword-mangled (`render_child` / the manifest
490
+ renderer closure both mangle every prop key via `jinja_ident` before
491
+ calling in -- see their docstrings), but `defaults`' own keys and each
492
+ entry's `propName` are the RAW (un-mangled) spellings `extractSsrDefaults`
493
+ emitted. For a reserved-word prop (`as`, `for`, `class`, ...) an
494
+ unmangled lookup/output key misses the mangled `props` map entirely --
495
+ silently falling back to the static default even when the caller DID
496
+ supply a value -- and then writes the stash under the RAW key, which a
497
+ subsequent mangling pass (or the template itself, keyed by the mangled
498
+ name) never reads. Both the output key and the `propName` lookup are
499
+ mangled here so this resolves correctly regardless of whether the prop
500
+ name happens to collide with a reserved word (#2524 follow-up)."""
488
501
  extra: dict = {}
489
502
  for name, d in (defaults or {}).items():
503
+ key = jinja_ident(name)
490
504
  if not isinstance(d, dict):
491
- extra[name] = d
505
+ extra[key] = d
492
506
  continue
493
507
  if d.get("isRestProps"):
494
- extra[name] = props[name] if name in props else d.get("value")
508
+ extra[key] = props[key] if key in props else d.get("value")
495
509
  continue
496
510
  prop_name = d.get("propName")
497
- if prop_name is not None and props.get(prop_name) is not None:
498
- extra[name] = props[prop_name]
511
+ mangled_prop_name = jinja_ident(prop_name) if prop_name is not None else None
512
+ if mangled_prop_name is not None and props.get(mangled_prop_name) is not None:
513
+ extra[key] = props[mangled_prop_name]
499
514
  else:
500
- extra[name] = d.get("value")
515
+ extra[key] = d.get("value")
501
516
  return extra
502
517
 
503
518
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Regression for the review finding on #2524's SSR half (BLOCKER 1): a
3
+ * reserved-word aliased/defaulted prop passed to a CHILD component used to
4
+ * be silently clobbered by the child's static default.
5
+ *
6
+ * `child_props` arrives at `_derive_stash_from_defaults` already
7
+ * keyword-mangled (`jinja_ident`, `runtime.py`) — `as` becomes `as_` — but
8
+ * the serialised `_defaults` map's own keys and each entry's `propName` were
9
+ * the RAW (un-mangled) spellings `extractSsrDefaults` emits. For a
10
+ * reserved-word prop, `_derive_stash_from_defaults` looked up `props['as']`
11
+ * (never present — the real key is `as_`), missed, and fell back to the
12
+ * static default under the RAW key `as`; after `_vars = {**child_props,
13
+ * **_extra}`, the WRONG static default won over the real caller value.
14
+ *
15
+ * `_derive_stash_from_defaults` now mangles both the output key and the
16
+ * `propName` lookup (mirrors the Rust runtime's `resolve_child_vars`), so
17
+ * this resolves correctly. See `runtime.py`'s `_derive_stash_from_defaults`
18
+ * docstring.
19
+ */
20
+ import { test, expect } from 'bun:test'
21
+ import { JinjaAdapter } from '../adapter'
22
+ import { renderJinjaComponent, PythonNotAvailableError } from '../test-render'
23
+
24
+ const SOURCE = `
25
+ import { Tag } from './Tag'
26
+ export function Wrapper() {
27
+ return <Tag as="section" label="hi" />
28
+ }
29
+ `
30
+
31
+ const TAG_SOURCE = `
32
+ export function Tag({ as = 'span', label }: { as?: string; label: string }) {
33
+ return <div>{as}:{label}</div>
34
+ }
35
+ `
36
+
37
+ test('a reserved-word aliased/defaulted prop survives the child-render path (jinja, #2524 follow-up)', async () => {
38
+ let html: string
39
+ try {
40
+ html = await renderJinjaComponent({
41
+ source: SOURCE,
42
+ adapter: new JinjaAdapter(),
43
+ components: { './Tag': TAG_SOURCE },
44
+ })
45
+ } catch (err) {
46
+ if (err instanceof PythonNotAvailableError) {
47
+ console.log(`Skipping: ${err.message}`)
48
+ return
49
+ }
50
+ throw err
51
+ }
52
+ // The caller passed `as="section"` — it must win over the child's own
53
+ // `as = 'span'` destructure default. Text is wrapped in bf slot markers,
54
+ // so match each interpolated value rather than the literal `x:y` shape.
55
+ expect(html).toContain('>section<')
56
+ expect(html).toContain('>hi<')
57
+ expect(html).not.toContain('>span<')
58
+ })
@@ -20,20 +20,4 @@ export const renderDivergences: RenderDivergences = {
20
20
  // instead of a fixed regex-shape catalogue) now correctly seeds `todos`
21
21
  // from `(props.initialTodos ?? []).map(t => ({ ...t, editing: false }))`.
22
22
 
23
- // Onboarding TSX-fidelity fixtures (PR #2461): `expectedHtml` was
24
- // hand-authored to the CORRECT output while the emission bug lived in
25
- // the shared compiler layer — every adapter, including the Hono
26
- // reference, used to emit the broken form. That shared-layer defect
27
- // (#2460) is now FIXED (b4f5075): `expectedHtml` is generated from the
28
- // Hono reference like any other fixture. The remaining gap is
29
- // per-template-adapter — this adapter still keys its template vars /
30
- // ssr-defaults / props bridge off the local binding instead of
31
- // `sourceName ?? name` — tracked by #2524 (the 7 silent template
32
- // adapters). Graduate by applying the same `sourceName ?? name` fix to
33
- // this adapter's emission path and deleting these lines (and the
34
- // matching hono `skipJsx` entries, already gone).
35
- 'aliased-destructured-prop':
36
- 'aliased destructured prop `{ n: count }` loses its rename — template vars, ssr-defaults, and the props bridge all key off the local name, so the prop is always undefined (https://github.com/piconic-ai/barefootjs/issues/2524)',
37
- 'composite-row-child-aliased-prop':
38
- 'same defect as `aliased-destructured-prop` (#2524), inside a keyed `.map()` loop row: the nested child\'s renamed prop (`{ n: count }`) is always undefined, so both rows render an empty count (https://github.com/piconic-ai/barefootjs/issues/2524)',
39
23
  }
@@ -16,8 +16,8 @@
16
16
  * generated render script (Python, not Perl) and its literal syntax differ.
17
17
  */
18
18
 
19
- import { compileJSX, extractSsrDefaults, importsSearchParams, evaluateSignalInit, tryEvaluateSignalInit } from '@barefootjs/jsx'
20
- import type { ComponentIR } from '@barefootjs/jsx'
19
+ import { compileJSX, extractSsrDefaults, deriveStashFromDefaults, importsSearchParams, evaluateSignalInit } from '@barefootjs/jsx'
20
+ import type { ComponentIR, SsrDefault } from '@barefootjs/jsx'
21
21
  import { mkdir, rm } from 'node:fs/promises'
22
22
  import { resolve } from 'node:path'
23
23
 
@@ -252,7 +252,7 @@ import uuid
252
252
 
253
253
  from barefootjs import BarefootJS, SearchParams
254
254
  from barefootjs.backend_jinja import JinjaBackend
255
- from barefootjs.runtime import jinja_ident
255
+ from barefootjs.runtime import jinja_ident, _derive_stash_from_defaults
256
256
 
257
257
  # Single Jinja2 backend over the temp template dir.
258
258
  backend = JinjaBackend(paths=[${pyStr(tempDir)}])
@@ -333,7 +333,16 @@ function collectImportedComponentNames(ir: ComponentIR): string[] {
333
333
  * `runtime.BarefootJS.render_child`'s docstring. So the rest-bag routing
334
334
  * below and the `_bf_slot` / `key` / `children` pops all compare against
335
335
  * ALREADY-mangled key spellings; the `keep` set mangles the child's declared
336
- * param names to match.
336
+ * param (CALLER-facing, `sourceName ?? name`) names to match.
337
+ *
338
+ * Template vars seed through the production `_derive_stash_from_defaults`
339
+ * (`barefootjs.runtime`, the same function `register_components_from_
340
+ * manifest`'s `make_renderer` calls) rather than a flat `{**_defaults,
341
+ * **child_props}` merge: the flat merge never resolves an aliased
342
+ * destructured prop's CALLER-facing key (`n`) onto its local template var
343
+ * (`count`) — `_defaults` carries the FULL `{value, propName?,
344
+ * isRestProps?}` shape (not flattened to bare values) so the propName
345
+ * resolution has something to read (#2524 SSR half).
337
346
  */
338
347
  function buildChildRenderers(
339
348
  childTemplates: Map<string, { template: string; ir: ComponentIR }>,
@@ -348,12 +357,14 @@ function buildChildRenderers(
348
357
  const snakeName = toSnakeCase(componentName)
349
358
  const fnSuffix = snakeName.replace(/[^a-zA-Z0-9_]/g, '_')
350
359
  // Statically-derived ssrDefaults the child template's vars seed from
351
- // (prop defaults + signal / memo initial values), serialised to a
352
- // Python dict literal.
360
+ // (prop defaults + signal / memo initial values), serialised VERBATIM
361
+ // (propName / isRestProps intact) to a Python dict literal — resolved
362
+ // per-call against the REAL child props by `_derive_stash_from_defaults`
363
+ // below, not flattened here.
353
364
  const ssrDefaults = extractSsrDefaults(childIR.metadata) ?? {}
354
365
  const defaultsPy = ssrDefaultsToPy(ssrDefaults)
355
366
  const restPropsName = childIR.metadata.restPropsName
356
- const paramNames = (childIR.metadata.propsParams ?? []).map(p => p.name)
367
+ const paramNames = (childIR.metadata.propsParams ?? []).map(p => p.sourceName ?? p.name)
357
368
 
358
369
  lines.push(`def _make_child_renderer_${fnSuffix}():`)
359
370
  lines.push(` _defaults = ${defaultsPy}`)
@@ -404,8 +415,15 @@ function buildChildRenderers(
404
415
  lines.push(` child_bf._child_renderers(bf._child_renderers())`)
405
416
  lines.push(` child_bf._scripts(bf._scripts())`)
406
417
  lines.push(` child_bf._script_seen(bf._script_seen())`)
407
- // Seed template vars: static ssrDefaults first, caller's props win.
408
- lines.push(` _vars = {**_defaults, **child_props}`)
418
+ // Seed template vars through the production `_derive_stash_from_defaults`
419
+ // — resolves each entry's `propName` against the REAL `child_props`
420
+ // (already keyword-mangled above), falling back to the static `value`;
421
+ // `isRestProps` entries pass the already-routed rest bag through. Caller
422
+ // props still win overall via the final merge (mirrors the ERB
423
+ // harness's `child_props.merge(extra)` and the production
424
+ // `register_components_from_manifest` renderer closure).
425
+ lines.push(` _extra = _derive_stash_from_defaults(_defaults, child_props)`)
426
+ lines.push(` _vars = {**child_props, **_extra}`)
409
427
  lines.push(` rendered = backend.render_named(${pyStr(snakeName)}, child_bf, _vars)`)
410
428
  lines.push(` if isinstance(rendered, str) and rendered.endswith('\\n'):`)
411
429
  lines.push(` rendered = rendered[:-1]`)
@@ -424,22 +442,31 @@ function pyList(names: string[]): string {
424
442
  return `[${names.map(pyStr).join(', ')}]`
425
443
  }
426
444
 
427
- /** Serialise an ssrDefaults map to a Python dict literal. */
428
- function ssrDefaultsToPy(defaults: Record<string, unknown>): string {
445
+ /**
446
+ * Serialise an ssrDefaults map to a Python dict literal, VERBATIM
447
+ * `value` / `propName` / `isRestProps` intact, exactly the shape
448
+ * `_derive_stash_from_defaults` expects (and exactly what the production
449
+ * build manifest embeds). Do NOT flatten to bare `value`s here: that was
450
+ * the #2524 SSR-half bug — a flattened entry has nothing left for the
451
+ * propName-aware resolution to read, so an aliased destructured prop's
452
+ * caller-facing key is silently dropped.
453
+ */
454
+ function ssrDefaultsToPy(defaults: Record<string, SsrDefault>): string {
429
455
  const entries: string[] = []
430
456
  for (const [name, d] of Object.entries(defaults)) {
431
- // ssrDefaults entries are `{ value, propName?, isRestProps? }` or a
432
- // bare value. The child renderer's caller props win, so we only need
433
- // the static fallback `value` here.
434
- let value: unknown = d
435
- if (d && typeof d === 'object' && 'value' in (d as Record<string, unknown>)) {
436
- value = (d as Record<string, unknown>).value
437
- }
438
- entries.push(`${pyStr(name)}: ${toPyLiteral(value)}`)
457
+ entries.push(`${pyStr(name)}: ${ssrDefaultEntryToPy(d)}`)
439
458
  }
440
459
  return `{${entries.join(', ')}}`
441
460
  }
442
461
 
462
+ /** Serialise a single `SsrDefault` entry to a Python dict literal. */
463
+ function ssrDefaultEntryToPy(d: SsrDefault): string {
464
+ const parts: string[] = [`'value': ${toPyLiteral(d.value)}`]
465
+ if (d.propName !== undefined) parts.push(`'propName': ${pyStr(d.propName)}`)
466
+ if (d.isRestProps) parts.push(`'isRestProps': True`)
467
+ return `{${parts.join(', ')}}`
468
+ }
469
+
443
470
  /**
444
471
  * Convert PascalCase to snake_case for template naming (matches the
445
472
  * adapter's `toTemplateName`).
@@ -465,23 +492,29 @@ function buildPythonProps(
465
492
  entries.push(`${pyStr('scope_id')}: ${pyStr(explicitScope)}`)
466
493
 
467
494
  // Prop params with defaults (before signals, so signals can reference them).
495
+ // Seeded through the shared `deriveStashFromDefaults` (the TS twin of the
496
+ // production `_derive_stash_from_defaults` this harness ALSO calls at
497
+ // render time for child components) so an aliased destructured prop's
498
+ // CALLER-facing key (`propName`, e.g. `n` for `{ n: count }`) is honoured,
499
+ // not just the local template var name (#2524 SSR half). `props` is keyed
500
+ // by the caller-facing name, exactly what `propName` resolves against.
501
+ const rootSsrDefaults = extractSsrDefaults(ir.metadata) ?? {}
502
+ const derivedProps = deriveStashFromDefaults(rootSsrDefaults, props ?? {})
468
503
  for (const param of ir.metadata.propsParams) {
469
- if (props && param.name in props) continue
470
- if (param.defaultValue) {
471
- const result = tryEvaluateSignalInit(param.defaultValue.trim(), props)
472
- if (result.ok) {
473
- entries.push(`${pyStr(param.name)}: ${toPyLiteral(result.value)}`)
474
- continue
475
- }
476
- }
504
+ if (param.isRest) continue
477
505
  // No default + no caller value: pass `None` so Jinja's var lookup for
478
506
  // an optional prop doesn't fault before its falsy branch elides.
479
- entries.push(`${pyStr(param.name)}: None`)
507
+ const value = derivedProps[param.name] ?? null
508
+ entries.push(`${pyStr(param.name)}: ${toPyLiteral(value)}`)
480
509
  }
481
510
 
482
511
  // Route undeclared props into the rest bag (`bf.spread_attrs($<rest>)`).
483
512
  const restPropsName = ir.metadata.restPropsName
484
- const declaredParams = new Set(ir.metadata.propsParams.map(p => p.name))
513
+ // Caller-facing keys an aliased destructured prop's DECLARED set for
514
+ // rest-bag routing must match what the caller actually sent (`n`), not
515
+ // the local binding (`count`), or the caller's own prop silently gets
516
+ // swept into the rest bag as an undeclared extra (#2524).
517
+ const declaredParams = new Set(ir.metadata.propsParams.map(p => p.sourceName ?? p.name))
485
518
  const restBagEntries: Array<[string, unknown]> = []
486
519
  if (restPropsName && props) {
487
520
  for (const [key, value] of Object.entries(props)) {
@@ -496,11 +529,20 @@ function buildPythonProps(
496
529
  entries.push(`${pyStr(restPropsName)}: ${toPyLiteral(Object.fromEntries(restBagEntries))}`)
497
530
  }
498
531
 
499
- // User props.
532
+ // User props. Skip a key that's already a declared param's LOCAL template
533
+ // var name — `derivedProps` above already resolved that var correctly
534
+ // (through `propName`, for an aliased prop); re-pushing the raw `props[key]`
535
+ // here would silently clobber it with an UNRELATED value whenever a caller
536
+ // happens to also pass a same-spelled-as-local-name prop that isn't this
537
+ // param's actual `propName` (#2524 — surfaced by the aliased-destructured-prop
538
+ // generated data points, which pass both `n` (the real propName) and an
539
+ // incidental `count` key).
540
+ const localParamNames = new Set(ir.metadata.propsParams.map(p => p.name))
500
541
  if (props) {
501
542
  for (const [key, value] of Object.entries(props)) {
502
543
  if (key.startsWith('__')) continue
503
544
  if (routedKeys.has(key)) continue
545
+ if (localParamNames.has(key)) continue
504
546
  if (typeof value === 'string') {
505
547
  entries.push(`${pyStr(key)}: ${pyStr(value)}`)
506
548
  } else if (typeof value === 'number') {
@@ -526,10 +568,9 @@ function buildPythonProps(
526
568
 
527
569
  // Memo values seeded from the statically-evaluated ssrDefaults, same
528
570
  // as the production plugin's before_render hook.
529
- const ssrDefaults = extractSsrDefaults(ir.metadata) ?? {}
530
571
  for (const memo of ir.metadata.memos) {
531
- const entry = ssrDefaults[memo.name]
532
- const value = entry && typeof entry === 'object' && 'value' in entry ? entry.value : 0
572
+ const entry = rootSsrDefaults[memo.name]
573
+ const value = entry ? entry.value : 0
533
574
  entries.push(`${pyStr(memo.name)}: ${toPyLiteral(value ?? 0)}`)
534
575
  }
535
576