@barefootjs/jinja 0.31.1 → 0.31.2

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
@@ -2880,13 +2987,13 @@ var KEYWORDS_AND_GLOBALS = new Set([
2880
2987
  ]);
2881
2988
 
2882
2989
  // ../jsx/src/ir-to-client-js/walk-prop-accesses.ts
2883
- import ts12 from "typescript";
2990
+ import ts13 from "typescript";
2884
2991
 
2885
2992
  // ../jsx/src/value-references.ts
2886
- import ts13 from "typescript";
2993
+ import ts14 from "typescript";
2887
2994
 
2888
2995
  // ../jsx/src/relocate.ts
2889
- import ts14 from "typescript";
2996
+ import ts15 from "typescript";
2890
2997
 
2891
2998
  // ../jsx/src/lowering-registry.ts
2892
2999
  var plugins = [];
@@ -3103,10 +3210,10 @@ function matchSearchParamsMethodCall(callee, args, localNames) {
3103
3210
  }
3104
3211
 
3105
3212
  // ../jsx/src/ir-to-client-js/prune-unused-prop-extractions.ts
3106
- import ts15 from "typescript";
3213
+ import ts16 from "typescript";
3107
3214
 
3108
3215
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
3109
- import ts16 from "typescript";
3216
+ import ts17 from "typescript";
3110
3217
  var NO_PREAMBLE = {
3111
3218
  lazySafe: true,
3112
3219
  facts: { declaredNames: new Set, freeNames: new Set }
@@ -3156,7 +3263,7 @@ var INERT_BINDING_GLOBALS = new Set([
3156
3263
  ]);
3157
3264
 
3158
3265
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
3159
- import ts17 from "typescript";
3266
+ import ts18 from "typescript";
3160
3267
 
3161
3268
  // ../jsx/src/ir-to-client-js/control-flow/stringify/event-delegation.ts
3162
3269
  var NON_BUBBLING_EVENTS = new Set([
@@ -3171,7 +3278,7 @@ var NON_BUBBLING_EVENTS = new Set([
3171
3278
  ]);
3172
3279
 
3173
3280
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
3174
- import ts18 from "typescript";
3281
+ import ts19 from "typescript";
3175
3282
 
3176
3283
  // ../jsx/src/ir-to-client-js/source-map.ts
3177
3284
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -3262,15 +3369,15 @@ class SourceMapGenerator {
3262
3369
  }
3263
3370
 
3264
3371
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
3265
- import ts19 from "typescript";
3372
+ import ts20 from "typescript";
3266
3373
 
3267
3374
  // ../jsx/src/ssr-defaults.ts
3268
- import ts20 from "typescript";
3375
+ import ts21 from "typescript";
3269
3376
  var UNRESOLVED = Symbol("unresolved");
3270
3377
  var NO_RETURN = Symbol("no-return");
3271
3378
 
3272
3379
  // ../jsx/src/augment-inherited-props.ts
3273
- import ts21 from "typescript";
3380
+ import ts22 from "typescript";
3274
3381
  function collectContextConsumers(metadata) {
3275
3382
  const constants = metadata.localConstants ?? [];
3276
3383
  const contextDefaults = new Map;
@@ -3302,47 +3409,47 @@ function collectContextConsumers(metadata) {
3302
3409
  }
3303
3410
  function parseUseContextArg(source) {
3304
3411
  const expr = parseSingleExpression(source);
3305
- if (!expr || !ts21.isCallExpression(expr))
3412
+ if (!expr || !ts22.isCallExpression(expr))
3306
3413
  return null;
3307
- if (!ts21.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3414
+ if (!ts22.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3308
3415
  return null;
3309
3416
  if (expr.arguments.length !== 1)
3310
3417
  return null;
3311
3418
  const arg = expr.arguments[0];
3312
- return ts21.isIdentifier(arg) ? arg.text : null;
3419
+ return ts22.isIdentifier(arg) ? arg.text : null;
3313
3420
  }
3314
3421
  function parseCreateContextDefault(source) {
3315
3422
  const expr = parseSingleExpression(source);
3316
- if (!expr || !ts21.isCallExpression(expr))
3423
+ if (!expr || !ts22.isCallExpression(expr))
3317
3424
  return null;
3318
3425
  if (expr.arguments.length === 0)
3319
3426
  return null;
3320
3427
  const arg = expr.arguments[0];
3321
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3428
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3322
3429
  return arg.text;
3323
- if (ts21.isNumericLiteral(arg))
3430
+ if (ts22.isNumericLiteral(arg))
3324
3431
  return Number(arg.text);
3325
- if (arg.kind === ts21.SyntaxKind.TrueKeyword)
3432
+ if (arg.kind === ts22.SyntaxKind.TrueKeyword)
3326
3433
  return true;
3327
- if (arg.kind === ts21.SyntaxKind.FalseKeyword)
3434
+ if (arg.kind === ts22.SyntaxKind.FalseKeyword)
3328
3435
  return false;
3329
3436
  return null;
3330
3437
  }
3331
3438
  function isObjectLiteralCreateContextDefault(source) {
3332
3439
  const expr = parseSingleExpression(source);
3333
- if (!expr || !ts21.isCallExpression(expr))
3440
+ if (!expr || !ts22.isCallExpression(expr))
3334
3441
  return false;
3335
3442
  if (expr.arguments.length === 0)
3336
3443
  return false;
3337
- return ts21.isObjectLiteralExpression(expr.arguments[0]);
3444
+ return ts22.isObjectLiteralExpression(expr.arguments[0]);
3338
3445
  }
3339
3446
  function parseSingleExpression(source) {
3340
- const sf = ts21.createSourceFile("__ctx.ts", `(${source})`, ts21.ScriptTarget.Latest, false);
3447
+ const sf = ts22.createSourceFile("__ctx.ts", `(${source})`, ts22.ScriptTarget.Latest, false);
3341
3448
  const stmt = sf.statements[0];
3342
- if (!stmt || !ts21.isExpressionStatement(stmt))
3449
+ if (!stmt || !ts22.isExpressionStatement(stmt))
3343
3450
  return null;
3344
3451
  let e = stmt.expression;
3345
- while (ts21.isParenthesizedExpression(e))
3452
+ while (ts22.isParenthesizedExpression(e))
3346
3453
  e = e.expression;
3347
3454
  return e;
3348
3455
  }
@@ -3367,25 +3474,25 @@ function augmentInheritedPropAccesses(ir) {
3367
3474
  const pinCoalesceLiterals = (s) => {
3368
3475
  if (!s || !s.includes(propsObj))
3369
3476
  return;
3370
- const sf = ts21.createSourceFile("__aug.ts", `(${s})`, ts21.ScriptTarget.Latest, false);
3477
+ const sf = ts22.createSourceFile("__aug.ts", `(${s})`, ts22.ScriptTarget.Latest, false);
3371
3478
  const visit = (n) => {
3372
- if (ts21.isBinaryExpression(n) && (n.operatorToken.kind === ts21.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts21.SyntaxKind.BarBarToken)) {
3479
+ if (ts22.isBinaryExpression(n) && (n.operatorToken.kind === ts22.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts22.SyntaxKind.BarBarToken)) {
3373
3480
  let left = n.left;
3374
- while (ts21.isParenthesizedExpression(left))
3481
+ while (ts22.isParenthesizedExpression(left))
3375
3482
  left = left.expression;
3376
- if (ts21.isPropertyAccessExpression(left) && ts21.isIdentifier(left.expression) && left.expression.text === propsObj) {
3483
+ if (ts22.isPropertyAccessExpression(left) && ts22.isIdentifier(left.expression) && left.expression.text === propsObj) {
3377
3484
  const name = left.name.text;
3378
3485
  let right = n.right;
3379
- while (ts21.isParenthesizedExpression(right))
3486
+ while (ts22.isParenthesizedExpression(right))
3380
3487
  right = right.expression;
3381
- if (ts21.isPrefixUnaryExpression(right))
3488
+ if (ts22.isPrefixUnaryExpression(right))
3382
3489
  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;
3490
+ const kind = ts22.isNumericLiteral(right) ? "number" : right.kind === ts22.SyntaxKind.TrueKeyword || right.kind === ts22.SyntaxKind.FalseKeyword ? "boolean" : ts22.isStringLiteralLike(right) ? "string" : null;
3384
3491
  if (kind && !coalesceLiteralTypes.has(name))
3385
3492
  coalesceLiteralTypes.set(name, kind);
3386
3493
  }
3387
3494
  }
3388
- ts21.forEachChild(n, visit);
3495
+ ts22.forEachChild(n, visit);
3389
3496
  };
3390
3497
  visit(sf);
3391
3498
  };
@@ -3496,33 +3603,33 @@ function augmentInheritedPropAccesses(ir) {
3496
3603
  }
3497
3604
  }
3498
3605
  function parseStaticStringConst(source) {
3499
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3606
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3500
3607
  const stmt = sf.statements[0];
3501
- if (!stmt || !ts21.isVariableStatement(stmt))
3608
+ if (!stmt || !ts22.isVariableStatement(stmt))
3502
3609
  return null;
3503
3610
  let init = stmt.declarationList.declarations[0]?.initializer;
3504
- while (init && ts21.isParenthesizedExpression(init))
3611
+ while (init && ts22.isParenthesizedExpression(init))
3505
3612
  init = init.expression;
3506
3613
  if (!init)
3507
3614
  return null;
3508
- if (ts21.isStringLiteral(init) || ts21.isNoSubstitutionTemplateLiteral(init)) {
3615
+ if (ts22.isStringLiteral(init) || ts22.isNoSubstitutionTemplateLiteral(init)) {
3509
3616
  return init.text;
3510
3617
  }
3511
3618
  return evalStringArrayJoin(source);
3512
3619
  }
3513
3620
  function evalTemplateOfStringConsts(source, resolved) {
3514
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3621
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3515
3622
  const stmt = sf.statements[0];
3516
- if (!stmt || !ts21.isVariableStatement(stmt))
3623
+ if (!stmt || !ts22.isVariableStatement(stmt))
3517
3624
  return null;
3518
3625
  let init = stmt.declarationList.declarations[0]?.initializer;
3519
- while (init && ts21.isParenthesizedExpression(init))
3626
+ while (init && ts22.isParenthesizedExpression(init))
3520
3627
  init = init.expression;
3521
- if (!init || !ts21.isTemplateExpression(init))
3628
+ if (!init || !ts22.isTemplateExpression(init))
3522
3629
  return null;
3523
3630
  let out = init.head.text;
3524
3631
  for (const span of init.templateSpans) {
3525
- if (!ts21.isIdentifier(span.expression))
3632
+ if (!ts22.isIdentifier(span.expression))
3526
3633
  return null;
3527
3634
  const value = resolved.get(span.expression.text);
3528
3635
  if (value === undefined)
@@ -3553,30 +3660,30 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3553
3660
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
3554
3661
  if (constInfo?.value === undefined)
3555
3662
  return null;
3556
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3663
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3557
3664
  if (sf.statements.length !== 1)
3558
3665
  return null;
3559
3666
  const stmt = sf.statements[0];
3560
- if (!ts21.isExpressionStatement(stmt))
3667
+ if (!ts22.isExpressionStatement(stmt))
3561
3668
  return null;
3562
3669
  let parsed = stmt.expression;
3563
- while (ts21.isParenthesizedExpression(parsed))
3670
+ while (ts22.isParenthesizedExpression(parsed))
3564
3671
  parsed = parsed.expression;
3565
- if (!ts21.isObjectLiteralExpression(parsed))
3672
+ if (!ts22.isObjectLiteralExpression(parsed))
3566
3673
  return null;
3567
3674
  for (const prop of parsed.properties) {
3568
- if (!ts21.isPropertyAssignment(prop))
3675
+ if (!ts22.isPropertyAssignment(prop))
3569
3676
  continue;
3570
3677
  const name = prop.name;
3571
- const propKey = ts21.isIdentifier(name) || ts21.isStringLiteral(name) || ts21.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3678
+ const propKey = ts22.isIdentifier(name) || ts22.isStringLiteral(name) || ts22.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3572
3679
  if (propKey !== key)
3573
3680
  continue;
3574
3681
  let v = prop.initializer;
3575
- while (ts21.isParenthesizedExpression(v))
3682
+ while (ts22.isParenthesizedExpression(v))
3576
3683
  v = v.expression;
3577
- if (ts21.isNumericLiteral(v))
3684
+ if (ts22.isNumericLiteral(v))
3578
3685
  return { kind: "number", text: v.text };
3579
- if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3686
+ if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3580
3687
  return { kind: "string", text: v.text };
3581
3688
  }
3582
3689
  return null;
@@ -3584,28 +3691,28 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3584
3691
  return null;
3585
3692
  }
3586
3693
  function evalStringArrayJoin(source) {
3587
- const sf = ts21.createSourceFile("__join.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3694
+ const sf = ts22.createSourceFile("__join.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3588
3695
  const stmt = sf.statements[0];
3589
- if (!stmt || !ts21.isVariableStatement(stmt))
3696
+ if (!stmt || !ts22.isVariableStatement(stmt))
3590
3697
  return null;
3591
3698
  let node = stmt.declarationList.declarations[0]?.initializer;
3592
- while (node && ts21.isParenthesizedExpression(node))
3699
+ while (node && ts22.isParenthesizedExpression(node))
3593
3700
  node = node.expression;
3594
- if (!node || !ts21.isCallExpression(node))
3701
+ if (!node || !ts22.isCallExpression(node))
3595
3702
  return null;
3596
3703
  const callee = node.expression;
3597
- if (!ts21.isPropertyAccessExpression(callee))
3704
+ if (!ts22.isPropertyAccessExpression(callee))
3598
3705
  return null;
3599
3706
  if (callee.name.text !== "join")
3600
3707
  return null;
3601
3708
  let recv = callee.expression;
3602
- while (ts21.isParenthesizedExpression(recv))
3709
+ while (ts22.isParenthesizedExpression(recv))
3603
3710
  recv = recv.expression;
3604
- if (!ts21.isArrayLiteralExpression(recv))
3711
+ if (!ts22.isArrayLiteralExpression(recv))
3605
3712
  return null;
3606
3713
  const parts = [];
3607
3714
  for (const el of recv.elements) {
3608
- if (ts21.isStringLiteral(el) || ts21.isNoSubstitutionTemplateLiteral(el)) {
3715
+ if (ts22.isStringLiteral(el) || ts22.isNoSubstitutionTemplateLiteral(el)) {
3609
3716
  parts.push(el.text);
3610
3717
  } else {
3611
3718
  return null;
@@ -3614,7 +3721,7 @@ function evalStringArrayJoin(source) {
3614
3721
  let sep = ",";
3615
3722
  if (node.arguments.length >= 1) {
3616
3723
  const arg = node.arguments[0];
3617
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3724
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3618
3725
  sep = arg.text;
3619
3726
  else
3620
3727
  return null;
@@ -3622,11 +3729,11 @@ function evalStringArrayJoin(source) {
3622
3729
  return parts.join(sep);
3623
3730
  }
3624
3731
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3625
- if (!ts21.isElementAccessExpression(val))
3732
+ if (!ts22.isElementAccessExpression(val))
3626
3733
  return null;
3627
3734
  const obj = val.expression;
3628
3735
  const arg = val.argumentExpression;
3629
- if (!ts21.isIdentifier(obj) || !ts21.isIdentifier(arg))
3736
+ if (!ts22.isIdentifier(obj) || !ts22.isIdentifier(arg))
3630
3737
  return null;
3631
3738
  let indexPropName;
3632
3739
  let defaultKey;
@@ -3642,35 +3749,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3642
3749
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
3643
3750
  if (constInfo?.value === undefined)
3644
3751
  return null;
3645
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3752
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3646
3753
  if (sf.statements.length !== 1)
3647
3754
  return null;
3648
3755
  const stmt = sf.statements[0];
3649
- if (!ts21.isExpressionStatement(stmt))
3756
+ if (!ts22.isExpressionStatement(stmt))
3650
3757
  return null;
3651
3758
  let parsed = stmt.expression;
3652
- while (ts21.isParenthesizedExpression(parsed))
3759
+ while (ts22.isParenthesizedExpression(parsed))
3653
3760
  parsed = parsed.expression;
3654
- if (!ts21.isObjectLiteralExpression(parsed))
3761
+ if (!ts22.isObjectLiteralExpression(parsed))
3655
3762
  return null;
3656
3763
  const entries = [];
3657
3764
  for (const prop of parsed.properties) {
3658
- if (!ts21.isPropertyAssignment(prop))
3765
+ if (!ts22.isPropertyAssignment(prop))
3659
3766
  return null;
3660
3767
  let key;
3661
- if (ts21.isIdentifier(prop.name)) {
3768
+ if (ts22.isIdentifier(prop.name)) {
3662
3769
  key = prop.name.text;
3663
- } else if (ts21.isStringLiteral(prop.name) || ts21.isNoSubstitutionTemplateLiteral(prop.name)) {
3770
+ } else if (ts22.isStringLiteral(prop.name) || ts22.isNoSubstitutionTemplateLiteral(prop.name)) {
3664
3771
  key = prop.name.text;
3665
3772
  } else {
3666
3773
  return null;
3667
3774
  }
3668
3775
  let v = prop.initializer;
3669
- while (ts21.isParenthesizedExpression(v))
3776
+ while (ts22.isParenthesizedExpression(v))
3670
3777
  v = v.expression;
3671
- if (ts21.isNumericLiteral(v)) {
3778
+ if (ts22.isNumericLiteral(v)) {
3672
3779
  entries.push({ key, value: { kind: "number", text: v.text } });
3673
- } else if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3780
+ } else if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3674
3781
  entries.push({ key, value: { kind: "string", text: v.text } });
3675
3782
  } else {
3676
3783
  return null;
@@ -3726,7 +3833,7 @@ function computeSsrSeedPlan(metadata) {
3726
3833
  // ../jsx/src/rich-type-refusal.ts
3727
3834
  var EMPTY_BINDINGS2 = new Map;
3728
3835
  // ../jsx/src/shared-program.ts
3729
- import ts22 from "typescript";
3836
+ import ts24 from "typescript";
3730
3837
  // ../jsx/src/adapters/interface.ts
3731
3838
  class BaseAdapter {
3732
3839
  renderChildren(children) {
@@ -3802,7 +3909,7 @@ class JsxAdapter extends BaseAdapter {
3802
3909
  }
3803
3910
  const rawInitialValue = preserveTypes ? signal.typedInitialValue ?? signal.initialValue : signal.initialValue;
3804
3911
  const initialValue = rawInitialValue.trim().startsWith("{") ? `(${rawInitialValue})` : rawInitialValue;
3805
- const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive";
3912
+ const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive" && signal.type.raw !== "object";
3806
3913
  if (needsTypeAssertion) {
3807
3914
  lines.push(` const ${signal.getter} = () => ${initialValue} as ${signal.type.raw}`);
3808
3915
  } else {
@@ -3821,12 +3928,16 @@ class JsxAdapter extends BaseAdapter {
3821
3928
  const computation = preserveTypes ? memo.typedComputation ?? memo.computation : memo.computation;
3822
3929
  lines.push(` const ${memo.name} = ${computation}`);
3823
3930
  }
3931
+ const moduleScopeNames = this.moduleScopeDeclarationNames(ir);
3824
3932
  for (const constant of ir.metadata.localConstants) {
3825
3933
  if (constant.isExported)
3826
3934
  continue;
3935
+ if (moduleScopeNames.has(constant.name))
3936
+ continue;
3827
3937
  const keyword = constant.declarationKind ?? "const";
3828
3938
  if (!constant.value) {
3829
- lines.push(` ${keyword} ${constant.name}`);
3939
+ const typeAnnotation = preserveTypes && constant.type ? `: ${constant.type.raw}` : "";
3940
+ lines.push(` ${keyword} ${constant.name}${typeAnnotation}`);
3830
3941
  continue;
3831
3942
  }
3832
3943
  const value = constant.value.trim();
@@ -3838,6 +3949,8 @@ class JsxAdapter extends BaseAdapter {
3838
3949
  lines.push(` ${keyword} ${constant.name} = ${constValue}`);
3839
3950
  }
3840
3951
  for (const func of localFunctions) {
3952
+ if (moduleScopeNames.has(func.name))
3953
+ continue;
3841
3954
  if (!reachable.has(func.name))
3842
3955
  continue;
3843
3956
  const params = preserveTypes && func.typedParams !== undefined ? func.typedParams : func.params.map(formatParamWithType).join(", ");
@@ -3847,6 +3960,125 @@ class JsxAdapter extends BaseAdapter {
3847
3960
  lines.push(` ${asyncKw}function ${func.name}(${params})${returnAnnotation} ${body}`);
3848
3961
  }
3849
3962
  return lines.join(`
3963
+ `);
3964
+ }
3965
+ moduleScopeNamesCache = new WeakMap;
3966
+ moduleScopeDeclarationNames(ir) {
3967
+ const cached = this.moduleScopeNamesCache.get(ir);
3968
+ if (cached)
3969
+ return cached;
3970
+ const componentScope = new Set;
3971
+ for (const sig of ir.metadata.signals) {
3972
+ if (sig.isModule)
3973
+ continue;
3974
+ componentScope.add(sig.getter);
3975
+ if (sig.setter)
3976
+ componentScope.add(sig.setter);
3977
+ }
3978
+ for (const memo of ir.metadata.memos) {
3979
+ if (!memo.isModule)
3980
+ componentScope.add(memo.name);
3981
+ }
3982
+ for (const p of ir.metadata.propsParams)
3983
+ componentScope.add(p.name);
3984
+ if (ir.metadata.propsObjectName)
3985
+ componentScope.add(ir.metadata.propsObjectName);
3986
+ if (ir.metadata.restPropsName)
3987
+ componentScope.add(ir.metadata.restPropsName);
3988
+ for (const c of ir.metadata.localConstants) {
3989
+ if (!c.isModule)
3990
+ componentScope.add(c.name);
3991
+ }
3992
+ for (const f of ir.metadata.localFunctions) {
3993
+ if (!f.isModule)
3994
+ componentScope.add(f.name);
3995
+ }
3996
+ const exported = new Set;
3997
+ const candidates = new Map;
3998
+ for (const c of ir.metadata.localConstants) {
3999
+ if (!c.isModule)
4000
+ continue;
4001
+ if (c.isJsx || c.isJsxFunction)
4002
+ continue;
4003
+ if (c.isExported) {
4004
+ exported.add(c.name);
4005
+ continue;
4006
+ }
4007
+ candidates.set(c.name, c.freeIdentifiers ?? extractFreeIdentifiersFromText(c.value ?? ""));
4008
+ }
4009
+ for (const f of ir.metadata.localFunctions) {
4010
+ if (!f.isModule)
4011
+ continue;
4012
+ if (f.isJsxFunction || f.isMultiReturnJsxHelper)
4013
+ continue;
4014
+ if (f.isExported) {
4015
+ exported.add(f.name);
4016
+ continue;
4017
+ }
4018
+ const params = f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4019
+ candidates.set(f.name, extractFreeIdentifiersFromText(`(${params}) => ${f.body}`));
4020
+ }
4021
+ const referencesAny = (refs, names) => {
4022
+ for (const ref of refs) {
4023
+ if (names.has(ref))
4024
+ return true;
4025
+ }
4026
+ return false;
4027
+ };
4028
+ let changed = true;
4029
+ while (changed) {
4030
+ changed = false;
4031
+ for (const [name, refs] of candidates) {
4032
+ if (referencesAny(refs, componentScope)) {
4033
+ candidates.delete(name);
4034
+ componentScope.add(name);
4035
+ changed = true;
4036
+ }
4037
+ }
4038
+ }
4039
+ const result = new Set([...exported, ...candidates.keys()]);
4040
+ this.moduleScopeNamesCache.set(ir, result);
4041
+ return result;
4042
+ }
4043
+ generateModuleScopeDeclarations(ir) {
4044
+ const { preserveTypes } = this.jsxConfig;
4045
+ const moduleNames = this.moduleScopeDeclarationNames(ir);
4046
+ const entries = [];
4047
+ for (const t of ir.metadata.typeDefinitions) {
4048
+ entries.push({ line: t.loc.start.line, text: t.definition });
4049
+ }
4050
+ for (const c of ir.metadata.localConstants) {
4051
+ if (!c.isModule || !moduleNames.has(c.name))
4052
+ continue;
4053
+ const keyword = c.declarationKind ?? "const";
4054
+ const exportKw = c.isExported ? "export " : "";
4055
+ if (!c.value) {
4056
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name}` });
4057
+ continue;
4058
+ }
4059
+ const trimmed = c.value.trim();
4060
+ if (/^new WeakMap\b/.test(trimmed))
4061
+ continue;
4062
+ if (c.isExported && /^createContext\b/.test(trimmed))
4063
+ continue;
4064
+ const value = preserveTypes ? c.typedValue ?? c.value : c.value;
4065
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name} = ${value}` });
4066
+ }
4067
+ for (const f of ir.metadata.localFunctions) {
4068
+ if (!f.isModule || !moduleNames.has(f.name))
4069
+ continue;
4070
+ const params = preserveTypes && f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4071
+ const returnAnnotation = preserveTypes && f.typedReturnType ? `: ${f.typedReturnType}` : "";
4072
+ const body = preserveTypes ? f.typedBody ?? f.body : f.body;
4073
+ const asyncKw = f.isAsync ? "async " : "";
4074
+ const exportKw = f.isExported ? "export " : "";
4075
+ entries.push({
4076
+ line: f.loc.start.line,
4077
+ text: `${exportKw}${asyncKw}function ${f.name}(${params})${returnAnnotation} ${body}`
4078
+ });
4079
+ }
4080
+ entries.sort((a, b) => a.line - b.line);
4081
+ return entries.map((e) => e.text).join(`
3850
4082
  `);
3851
4083
  }
3852
4084
  renderNodeRaw(node) {
@@ -3858,6 +4090,15 @@ class JsxAdapter extends BaseAdapter {
3858
4090
  }
3859
4091
  return this.renderNode(node);
3860
4092
  }
4093
+ renderTemplatePartsAsJs(parts) {
4094
+ return templatePartsToJsExpr(parts, { typed: this.jsxConfig.preserveTypes });
4095
+ }
4096
+ expressionValueToJs(value) {
4097
+ if (this.jsxConfig.preserveTypes && value.parts && value.expr === templatePartsToJsExpr(value.parts)) {
4098
+ return this.renderTemplatePartsAsJs(value.parts);
4099
+ }
4100
+ return value.expr;
4101
+ }
3861
4102
  renderScopeMarker(instanceIdExpr) {
3862
4103
  return `${BF_SCOPE}={${instanceIdExpr}}`;
3863
4104
  }
@@ -3925,6 +4166,7 @@ class TestAdapter extends JsxAdapter {
3925
4166
  generate(ir) {
3926
4167
  this.componentName = ir.metadata.componentName;
3927
4168
  const imports = this.generateImports(ir);
4169
+ const moduleConstants = this.generateModuleScopeDeclarations(ir);
3928
4170
  const types = this.generateTypes(ir);
3929
4171
  const component = this.generateComponent(ir);
3930
4172
  const defaultExport = ir.metadata.hasDefaultExport ? `
@@ -3933,9 +4175,11 @@ export default ${this.componentName}` : "";
3933
4175
  imports,
3934
4176
  types: types || "",
3935
4177
  component,
3936
- defaultExport
4178
+ defaultExport,
4179
+ moduleConstants,
4180
+ moduleConstantsIncludeExports: true
3937
4181
  };
3938
- const template = [imports, types, component].filter(Boolean).join(`
4182
+ const template = [imports, moduleConstants, types, component].filter(Boolean).join(`
3939
4183
 
3940
4184
  `) + defaultExport;
3941
4185
  return {
@@ -3966,9 +4210,6 @@ export default ${this.componentName}` : "";
3966
4210
  }
3967
4211
  generateTypes(ir) {
3968
4212
  const lines = [];
3969
- for (const typeDef of ir.metadata.typeDefinitions) {
3970
- lines.push(typeDef.definition);
3971
- }
3972
4213
  const propsTypeName = ir.metadata.propsType?.raw;
3973
4214
  if (propsTypeName && !ir.metadata.propsObjectName) {
3974
4215
  lines.push("");
@@ -3991,7 +4232,7 @@ export default ${this.componentName}` : "";
3991
4232
  const bodyRefText = [jsxBody, signalInits, scopeIdLine].join(`
3992
4233
  `);
3993
4234
  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(", ");
4235
+ const propsParams = ir.metadata.propsParams.map((p) => propsDestructureBinding(p)).join(", ");
3995
4236
  const restPropsName = ir.metadata.restPropsName;
3996
4237
  const hydrationProps = `__instanceId, ${bfScopeAlias}`;
3997
4238
  const parts = [];
@@ -4138,13 +4379,7 @@ export default ${this.componentName}` : "";
4138
4379
  }
4139
4380
  flattenTemplate(value) {
4140
4381
  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("") + "`";
4382
+ return this.renderTemplatePartsAsJs(v.parts);
4148
4383
  }
4149
4384
  renderComponentProps(comp) {
4150
4385
  const parts = [];
@@ -4618,7 +4853,7 @@ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
4618
4853
  };
4619
4854
  }
4620
4855
  // ../jsx/src/combine-client-js.ts
4621
- import ts23 from "typescript";
4856
+ import ts25 from "typescript";
4622
4857
  // ../jsx/src/loop-destructure.ts
4623
4858
  function isLowerableLoopDestructure(loop) {
4624
4859
  const bindings = loop.paramBindings;
@@ -4758,9 +4993,9 @@ function escapeRe(s) {
4758
4993
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4759
4994
  }
4760
4995
  // ../jsx/src/debug.ts
4761
- import ts24 from "typescript";
4996
+ import ts26 from "typescript";
4762
4997
  // ../jsx/src/profiler.ts
4763
- import ts25 from "typescript";
4998
+ import ts27 from "typescript";
4764
4999
 
4765
5000
  // ../jsx/src/index.ts
4766
5001
  registerBuiltinLoweringPlugins();
@@ -5586,7 +5821,7 @@ function collectImportedLoopChildComponentErrors(ir, componentName) {
5586
5821
  }
5587
5822
 
5588
5823
  // src/adapter/spread/spread-codegen.ts
5589
- import ts26 from "typescript";
5824
+ import ts28 from "typescript";
5590
5825
  function conditionalSpreadToJinja(ctx, expr) {
5591
5826
  if (!expr || expr.kind !== "conditional")
5592
5827
  return null;
@@ -5641,7 +5876,7 @@ function recordIndexAccessToJinja(ctx, val) {
5641
5876
  if (val.kind !== "index-access" || val.object.kind !== "identifier" || val.index.kind !== "identifier") {
5642
5877
  return null;
5643
5878
  }
5644
- const tsVal = ts26.factory.createElementAccessExpression(ts26.factory.createIdentifier(val.object.name), ts26.factory.createIdentifier(val.index.name));
5879
+ const tsVal = ts28.factory.createElementAccessExpression(ts28.factory.createIdentifier(val.object.name), ts28.factory.createIdentifier(val.index.name));
5645
5880
  const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants ?? [], ctx.propsParams);
5646
5881
  if (!parsed)
5647
5882
  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.2",
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.2"
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.2",
74
+ "@barefootjs/vite": "0.31.2",
75
+ "@barefootjs/client": "0.31.2",
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