@barefootjs/mojolicious 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
@@ -1941,10 +1941,7 @@ var conformancePins = {
1941
1941
  "date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2356" }]
1942
1942
  };
1943
1943
  // src/render-divergences.ts
1944
- var renderDivergences = {
1945
- "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)",
1946
- "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)"
1947
- };
1944
+ var renderDivergences = {};
1948
1945
  export {
1949
1946
  renderDivergences,
1950
1947
  mojoAdapter,
@@ -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";
@@ -1922,6 +1925,29 @@ import ts5 from "typescript";
1922
1925
  // ../jsx/src/ir-to-client-js/utils.ts
1923
1926
  import ts3 from "typescript";
1924
1927
 
1928
+ // ../jsx/src/template-parts.ts
1929
+ function lookupPartToJsExpr(part, opts) {
1930
+ const key = opts?.useTemplate && part.templateKey ? part.templateKey : part.key;
1931
+ const obj = "{" + Object.entries(part.cases).map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`).join(", ") + "}";
1932
+ const typed = opts?.typed ? " as Record<string, string>" : "";
1933
+ return `(${obj}${typed})[${key}]`;
1934
+ }
1935
+ function templatePartsToJsExpr(parts, opts) {
1936
+ let result = "`";
1937
+ for (const part of parts) {
1938
+ if (part.type === "string") {
1939
+ result += opts?.useTemplate && part.templateValue ? part.templateValue : part.value;
1940
+ } else if (part.type === "ternary") {
1941
+ const cond = opts?.useTemplate && part.templateCondition ? part.templateCondition : part.condition;
1942
+ result += `\${${cond} ? '${part.whenTrue}' : '${part.whenFalse}'}`;
1943
+ } else if (part.type === "lookup") {
1944
+ result += `\${${lookupPartToJsExpr(part, opts)}}`;
1945
+ }
1946
+ }
1947
+ result += "`";
1948
+ return result;
1949
+ }
1950
+
1925
1951
  // ../jsx/src/scanner/js-scanner.ts
1926
1952
  import ts2 from "typescript";
1927
1953
 
@@ -2023,6 +2049,16 @@ function escapeHtml(text) {
2023
2049
  }
2024
2050
  // ../jsx/src/ir-to-client-js/csr-substitute.ts
2025
2051
  import ts4 from "typescript";
2052
+ function extractFreeIdentifiersFromText(text) {
2053
+ if (!text || text.trim().length === 0)
2054
+ return new Set;
2055
+ const sf = ts4.createSourceFile("__free_ids__.ts", `(${text});`, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TS);
2056
+ const stmt = sf.statements[0];
2057
+ if (!stmt || !ts4.isExpressionStatement(stmt))
2058
+ return new Set;
2059
+ const expr = ts4.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2060
+ return extractFreeIdentifiersFromNode(expr);
2061
+ }
2026
2062
 
2027
2063
  // ../jsx/src/adapters/child-scope.ts
2028
2064
  function derivesScopeFromSlot(comp) {
@@ -2073,6 +2109,27 @@ var SKELETON_PATH_FORCE_CLOSE_GROUPS = [
2073
2109
  new Set(["li"])
2074
2110
  ];
2075
2111
 
2112
+ // ../jsx/src/props-binding.ts
2113
+ import ts6 from "typescript";
2114
+ function isIdentifierName(key) {
2115
+ if (key.length === 0)
2116
+ return false;
2117
+ for (let i = 0;i < key.length; ) {
2118
+ const cp = key.codePointAt(i);
2119
+ const ok = i === 0 ? ts6.isIdentifierStart(cp, ts6.ScriptTarget.Latest) : ts6.isIdentifierPart(cp, ts6.ScriptTarget.Latest);
2120
+ if (!ok)
2121
+ return false;
2122
+ i += cp > 65535 ? 2 : 1;
2123
+ }
2124
+ return true;
2125
+ }
2126
+ function propsDestructureBinding(p) {
2127
+ const callerKey = p.sourceName ?? p.name;
2128
+ const localName = p.name;
2129
+ const binding = callerKey === localName ? localName : `${isIdentifierName(callerKey) ? callerKey : JSON.stringify(callerKey)}: ${localName}`;
2130
+ return p.defaultValue ? `${binding} = ${p.defaultValue}` : binding;
2131
+ }
2132
+
2076
2133
  // ../jsx/src/instrumentation.ts
2077
2134
  var _counters = freshCounters();
2078
2135
  function freshCounters() {
@@ -2086,14 +2143,14 @@ function freshCounters() {
2086
2143
  }
2087
2144
 
2088
2145
  // ../jsx/src/analyzer-context.ts
2089
- import ts7 from "typescript";
2146
+ import ts8 from "typescript";
2090
2147
 
2091
2148
  // ../jsx/src/strip-types.ts
2092
- import ts6 from "typescript";
2149
+ import ts7 from "typescript";
2093
2150
 
2094
2151
  // ../jsx/src/analyzer-context.ts
2095
- var _typePrinter = ts7.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2096
- var _blankTypeSourceFile = ts7.createSourceFile("__bf_types__.ts", "", ts7.ScriptTarget.Latest);
2152
+ var _typePrinter = ts8.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2153
+ var _blankTypeSourceFile = ts8.createSourceFile("__bf_types__.ts", "", ts8.ScriptTarget.Latest);
2097
2154
 
2098
2155
  // ../jsx/src/errors.ts
2099
2156
  var ErrorCodes = {
@@ -2109,6 +2166,7 @@ var ErrorCodes = {
2109
2166
  JSX_IN_LOCAL_FUNCTION: "BF045",
2110
2167
  COMPONENT_REQUIRED_PROP_MISSING: "BF046",
2111
2168
  JSX_BRANCH_LOCAL_IN_CALLBACK: "BF047",
2169
+ SIBLING_COMPONENT_NOT_COMPILED: "BF048",
2112
2170
  SHARED_PROGRAM_REQUIRED: "BF050",
2113
2171
  WRONG_PACKAGE_IMPORT: "BF051",
2114
2172
  BUILTIN_REQUIRES_IMPORT: "BF054",
@@ -2138,6 +2196,7 @@ var errorMessages = {
2138
2196
  [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.",
2139
2197
  [ErrorCodes.COMPONENT_REQUIRED_PROP_MISSING]: "Built-in component is missing a required prop.",
2140
2198
  [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>`.",
2199
+ [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.",
2141
2200
  [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.",
2142
2201
  [ErrorCodes.WRONG_PACKAGE_IMPORT]: "Import from wrong package.",
2143
2202
  [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.",
@@ -2311,6 +2370,54 @@ var CLIENT_EXPORTS = new Set([
2311
2370
  "Async",
2312
2371
  "Region"
2313
2372
  ]);
2373
+ function extractFreeIdentifiersFromNode(node) {
2374
+ const ids = new Set;
2375
+ const boundNames = new Set;
2376
+ function addBindingNames(name, out) {
2377
+ if (ts9.isIdentifier(name))
2378
+ out.push(name.text);
2379
+ else if (ts9.isObjectBindingPattern(name))
2380
+ name.elements.forEach((e) => addBindingNames(e.name, out));
2381
+ else if (ts9.isArrayBindingPattern(name))
2382
+ name.elements.forEach((e) => {
2383
+ if (!ts9.isOmittedExpression(e))
2384
+ addBindingNames(e.name, out);
2385
+ });
2386
+ }
2387
+ function visit(n) {
2388
+ if (ts9.isTypeNode(n))
2389
+ return;
2390
+ if (ts9.isIdentifier(n)) {
2391
+ const parent = n.parent;
2392
+ if (parent && ts9.isPropertyAccessExpression(parent) && parent.name === n)
2393
+ return;
2394
+ if (parent && ts9.isPropertyAssignment(parent) && parent.name === n)
2395
+ return;
2396
+ if (parent && ts9.isParameter(parent) && parent.name === n)
2397
+ return;
2398
+ if (parent && ts9.isVariableDeclaration(parent) && parent.name === n)
2399
+ return;
2400
+ if (boundNames.has(n.text))
2401
+ return;
2402
+ ids.add(n.text);
2403
+ return;
2404
+ }
2405
+ if (ts9.isArrowFunction(n)) {
2406
+ const params = [];
2407
+ for (const p of n.parameters)
2408
+ addBindingNames(p.name, params);
2409
+ for (const name of params)
2410
+ boundNames.add(name);
2411
+ ts9.forEachChild(n, visit);
2412
+ for (const name of params)
2413
+ boundNames.delete(name);
2414
+ return;
2415
+ }
2416
+ ts9.forEachChild(n, visit);
2417
+ }
2418
+ visit(node);
2419
+ return ids;
2420
+ }
2314
2421
  var BROWSER_ONLY_CLIENT_APIS = new Set([
2315
2422
  "useContext",
2316
2423
  "provideContext",
@@ -2329,7 +2436,7 @@ var REACTIVE_PRIMITIVES = new Set([
2329
2436
  ]);
2330
2437
 
2331
2438
  // ../jsx/src/jsx-to-ir.ts
2332
- import ts11 from "typescript";
2439
+ import ts12 from "typescript";
2333
2440
 
2334
2441
  // ../jsx/src/types.ts
2335
2442
  var SCOPE_FORBIDDEN = {
@@ -2411,10 +2518,10 @@ function findReachableNames(primaryRefs, declarations) {
2411
2518
  }
2412
2519
 
2413
2520
  // ../jsx/src/reactivity-checker.ts
2414
- import ts9 from "typescript";
2521
+ import ts10 from "typescript";
2415
2522
 
2416
2523
  // ../jsx/src/free-refs.ts
2417
- import ts10 from "typescript";
2524
+ import ts11 from "typescript";
2418
2525
  var _bindingMapCache = new WeakMap;
2419
2526
 
2420
2527
  // ../jsx/src/to-locale-date-lowering.ts
@@ -2732,6 +2839,83 @@ var toLocaleDatePlugin = {
2732
2839
  }
2733
2840
  };
2734
2841
 
2842
+ // ../jsx/src/scope/binding-scope.ts
2843
+ class BindingScope {
2844
+ frames;
2845
+ static EMPTY = new BindingScope([]);
2846
+ constructor(frames) {
2847
+ this.frames = frames;
2848
+ }
2849
+ enterLoopRow(loop) {
2850
+ const bindings = new Map;
2851
+ if (loop.paramBindings && loop.paramBindings.length > 0) {
2852
+ for (const b of loop.paramBindings)
2853
+ bindings.set(b.name, { source: "destructure" });
2854
+ } else {
2855
+ bindings.set(loop.param, { source: "item" });
2856
+ }
2857
+ if (loop.index != null)
2858
+ bindings.set(loop.index, { source: "index" });
2859
+ for (const name of loop.preamble?.declaredNames ?? [])
2860
+ bindings.set(name, { source: "preamble" });
2861
+ const frame = { kind: "loop-row", bindings };
2862
+ return new BindingScope([frame, ...this.frames]);
2863
+ }
2864
+ enterCallback(params) {
2865
+ const bindings = new Map;
2866
+ for (const name of params)
2867
+ bindings.set(name, { source: "param" });
2868
+ const frame = { kind: "callback", bindings };
2869
+ return new BindingScope([frame, ...this.frames]);
2870
+ }
2871
+ isBound(name) {
2872
+ for (const frame of this.frames) {
2873
+ if (frame.bindings.has(name))
2874
+ return true;
2875
+ }
2876
+ return false;
2877
+ }
2878
+ lookup(name) {
2879
+ for (let depth = 0;depth < this.frames.length; depth++) {
2880
+ const frame = this.frames[depth];
2881
+ const binding = frame.bindings.get(name);
2882
+ if (binding)
2883
+ return { depth, frame, binding };
2884
+ }
2885
+ return null;
2886
+ }
2887
+ boundNames() {
2888
+ if (this.boundNamesCache)
2889
+ return this.boundNamesCache;
2890
+ const names = new Set;
2891
+ for (const frame of this.frames) {
2892
+ for (const name of frame.bindings.keys())
2893
+ names.add(name);
2894
+ }
2895
+ this.boundNamesCache = names;
2896
+ return names;
2897
+ }
2898
+ boundNamesCache;
2899
+ valueBoundNamesCache;
2900
+ valueBoundNames() {
2901
+ if (this.valueBoundNamesCache)
2902
+ return this.valueBoundNamesCache;
2903
+ const names = new Set;
2904
+ for (const frame of this.frames) {
2905
+ for (const [name, binding] of frame.bindings) {
2906
+ if (binding.source === "item" || binding.source === "index" || binding.source === "destructure") {
2907
+ names.add(name);
2908
+ }
2909
+ }
2910
+ }
2911
+ this.valueBoundNamesCache = names;
2912
+ return names;
2913
+ }
2914
+ asShadowPredicate() {
2915
+ return (name) => this.isBound(name);
2916
+ }
2917
+ }
2918
+
2735
2919
  // ../jsx/src/jsx-to-ir.ts
2736
2920
  var EMPTY_BOUND = new Set;
2737
2921
  var constInitializerCache = new WeakMap;
@@ -2828,13 +3012,13 @@ var KEYWORDS_AND_GLOBALS = new Set([
2828
3012
  ]);
2829
3013
 
2830
3014
  // ../jsx/src/ir-to-client-js/walk-prop-accesses.ts
2831
- import ts12 from "typescript";
3015
+ import ts13 from "typescript";
2832
3016
 
2833
3017
  // ../jsx/src/value-references.ts
2834
- import ts13 from "typescript";
3018
+ import ts14 from "typescript";
2835
3019
 
2836
3020
  // ../jsx/src/relocate.ts
2837
- import ts14 from "typescript";
3021
+ import ts15 from "typescript";
2838
3022
 
2839
3023
  // ../jsx/src/lowering-registry.ts
2840
3024
  var plugins = [];
@@ -3051,10 +3235,10 @@ function matchSearchParamsMethodCall(callee, args, localNames) {
3051
3235
  }
3052
3236
 
3053
3237
  // ../jsx/src/ir-to-client-js/prune-unused-prop-extractions.ts
3054
- import ts15 from "typescript";
3238
+ import ts16 from "typescript";
3055
3239
 
3056
3240
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
3057
- import ts16 from "typescript";
3241
+ import ts17 from "typescript";
3058
3242
  var NO_PREAMBLE = {
3059
3243
  lazySafe: true,
3060
3244
  facts: { declaredNames: new Set, freeNames: new Set }
@@ -3104,7 +3288,7 @@ var INERT_BINDING_GLOBALS = new Set([
3104
3288
  ]);
3105
3289
 
3106
3290
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
3107
- import ts17 from "typescript";
3291
+ import ts18 from "typescript";
3108
3292
 
3109
3293
  // ../jsx/src/ir-to-client-js/control-flow/stringify/event-delegation.ts
3110
3294
  var NON_BUBBLING_EVENTS = new Set([
@@ -3119,7 +3303,7 @@ var NON_BUBBLING_EVENTS = new Set([
3119
3303
  ]);
3120
3304
 
3121
3305
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
3122
- import ts18 from "typescript";
3306
+ import ts19 from "typescript";
3123
3307
 
3124
3308
  // ../jsx/src/ir-to-client-js/source-map.ts
3125
3309
  var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -3210,15 +3394,15 @@ class SourceMapGenerator {
3210
3394
  }
3211
3395
 
3212
3396
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
3213
- import ts19 from "typescript";
3397
+ import ts20 from "typescript";
3214
3398
 
3215
3399
  // ../jsx/src/ssr-defaults.ts
3216
- import ts20 from "typescript";
3400
+ import ts21 from "typescript";
3217
3401
  var UNRESOLVED = Symbol("unresolved");
3218
3402
  var NO_RETURN = Symbol("no-return");
3219
3403
 
3220
3404
  // ../jsx/src/augment-inherited-props.ts
3221
- import ts21 from "typescript";
3405
+ import ts22 from "typescript";
3222
3406
  function collectContextConsumers(metadata) {
3223
3407
  const constants = metadata.localConstants ?? [];
3224
3408
  const contextDefaults = new Map;
@@ -3250,47 +3434,47 @@ function collectContextConsumers(metadata) {
3250
3434
  }
3251
3435
  function parseUseContextArg(source) {
3252
3436
  const expr = parseSingleExpression(source);
3253
- if (!expr || !ts21.isCallExpression(expr))
3437
+ if (!expr || !ts22.isCallExpression(expr))
3254
3438
  return null;
3255
- if (!ts21.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3439
+ if (!ts22.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
3256
3440
  return null;
3257
3441
  if (expr.arguments.length !== 1)
3258
3442
  return null;
3259
3443
  const arg = expr.arguments[0];
3260
- return ts21.isIdentifier(arg) ? arg.text : null;
3444
+ return ts22.isIdentifier(arg) ? arg.text : null;
3261
3445
  }
3262
3446
  function parseCreateContextDefault(source) {
3263
3447
  const expr = parseSingleExpression(source);
3264
- if (!expr || !ts21.isCallExpression(expr))
3448
+ if (!expr || !ts22.isCallExpression(expr))
3265
3449
  return null;
3266
3450
  if (expr.arguments.length === 0)
3267
3451
  return null;
3268
3452
  const arg = expr.arguments[0];
3269
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3453
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3270
3454
  return arg.text;
3271
- if (ts21.isNumericLiteral(arg))
3455
+ if (ts22.isNumericLiteral(arg))
3272
3456
  return Number(arg.text);
3273
- if (arg.kind === ts21.SyntaxKind.TrueKeyword)
3457
+ if (arg.kind === ts22.SyntaxKind.TrueKeyword)
3274
3458
  return true;
3275
- if (arg.kind === ts21.SyntaxKind.FalseKeyword)
3459
+ if (arg.kind === ts22.SyntaxKind.FalseKeyword)
3276
3460
  return false;
3277
3461
  return null;
3278
3462
  }
3279
3463
  function isObjectLiteralCreateContextDefault(source) {
3280
3464
  const expr = parseSingleExpression(source);
3281
- if (!expr || !ts21.isCallExpression(expr))
3465
+ if (!expr || !ts22.isCallExpression(expr))
3282
3466
  return false;
3283
3467
  if (expr.arguments.length === 0)
3284
3468
  return false;
3285
- return ts21.isObjectLiteralExpression(expr.arguments[0]);
3469
+ return ts22.isObjectLiteralExpression(expr.arguments[0]);
3286
3470
  }
3287
3471
  function parseSingleExpression(source) {
3288
- const sf = ts21.createSourceFile("__ctx.ts", `(${source})`, ts21.ScriptTarget.Latest, false);
3472
+ const sf = ts22.createSourceFile("__ctx.ts", `(${source})`, ts22.ScriptTarget.Latest, false);
3289
3473
  const stmt = sf.statements[0];
3290
- if (!stmt || !ts21.isExpressionStatement(stmt))
3474
+ if (!stmt || !ts22.isExpressionStatement(stmt))
3291
3475
  return null;
3292
3476
  let e = stmt.expression;
3293
- while (ts21.isParenthesizedExpression(e))
3477
+ while (ts22.isParenthesizedExpression(e))
3294
3478
  e = e.expression;
3295
3479
  return e;
3296
3480
  }
@@ -3315,25 +3499,25 @@ function augmentInheritedPropAccesses(ir) {
3315
3499
  const pinCoalesceLiterals = (s) => {
3316
3500
  if (!s || !s.includes(propsObj))
3317
3501
  return;
3318
- const sf = ts21.createSourceFile("__aug.ts", `(${s})`, ts21.ScriptTarget.Latest, false);
3502
+ const sf = ts22.createSourceFile("__aug.ts", `(${s})`, ts22.ScriptTarget.Latest, false);
3319
3503
  const visit = (n) => {
3320
- if (ts21.isBinaryExpression(n) && (n.operatorToken.kind === ts21.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts21.SyntaxKind.BarBarToken)) {
3504
+ if (ts22.isBinaryExpression(n) && (n.operatorToken.kind === ts22.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts22.SyntaxKind.BarBarToken)) {
3321
3505
  let left = n.left;
3322
- while (ts21.isParenthesizedExpression(left))
3506
+ while (ts22.isParenthesizedExpression(left))
3323
3507
  left = left.expression;
3324
- if (ts21.isPropertyAccessExpression(left) && ts21.isIdentifier(left.expression) && left.expression.text === propsObj) {
3508
+ if (ts22.isPropertyAccessExpression(left) && ts22.isIdentifier(left.expression) && left.expression.text === propsObj) {
3325
3509
  const name = left.name.text;
3326
3510
  let right = n.right;
3327
- while (ts21.isParenthesizedExpression(right))
3511
+ while (ts22.isParenthesizedExpression(right))
3328
3512
  right = right.expression;
3329
- if (ts21.isPrefixUnaryExpression(right))
3513
+ if (ts22.isPrefixUnaryExpression(right))
3330
3514
  right = right.operand;
3331
- const kind = ts21.isNumericLiteral(right) ? "number" : right.kind === ts21.SyntaxKind.TrueKeyword || right.kind === ts21.SyntaxKind.FalseKeyword ? "boolean" : ts21.isStringLiteralLike(right) ? "string" : null;
3515
+ const kind = ts22.isNumericLiteral(right) ? "number" : right.kind === ts22.SyntaxKind.TrueKeyword || right.kind === ts22.SyntaxKind.FalseKeyword ? "boolean" : ts22.isStringLiteralLike(right) ? "string" : null;
3332
3516
  if (kind && !coalesceLiteralTypes.has(name))
3333
3517
  coalesceLiteralTypes.set(name, kind);
3334
3518
  }
3335
3519
  }
3336
- ts21.forEachChild(n, visit);
3520
+ ts22.forEachChild(n, visit);
3337
3521
  };
3338
3522
  visit(sf);
3339
3523
  };
@@ -3444,33 +3628,33 @@ function augmentInheritedPropAccesses(ir) {
3444
3628
  }
3445
3629
  }
3446
3630
  function parseStaticStringConst(source) {
3447
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3631
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3448
3632
  const stmt = sf.statements[0];
3449
- if (!stmt || !ts21.isVariableStatement(stmt))
3633
+ if (!stmt || !ts22.isVariableStatement(stmt))
3450
3634
  return null;
3451
3635
  let init = stmt.declarationList.declarations[0]?.initializer;
3452
- while (init && ts21.isParenthesizedExpression(init))
3636
+ while (init && ts22.isParenthesizedExpression(init))
3453
3637
  init = init.expression;
3454
3638
  if (!init)
3455
3639
  return null;
3456
- if (ts21.isStringLiteral(init) || ts21.isNoSubstitutionTemplateLiteral(init)) {
3640
+ if (ts22.isStringLiteral(init) || ts22.isNoSubstitutionTemplateLiteral(init)) {
3457
3641
  return init.text;
3458
3642
  }
3459
3643
  return evalStringArrayJoin(source);
3460
3644
  }
3461
3645
  function evalTemplateOfStringConsts(source, resolved) {
3462
- const sf = ts21.createSourceFile("__const.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3646
+ const sf = ts22.createSourceFile("__const.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3463
3647
  const stmt = sf.statements[0];
3464
- if (!stmt || !ts21.isVariableStatement(stmt))
3648
+ if (!stmt || !ts22.isVariableStatement(stmt))
3465
3649
  return null;
3466
3650
  let init = stmt.declarationList.declarations[0]?.initializer;
3467
- while (init && ts21.isParenthesizedExpression(init))
3651
+ while (init && ts22.isParenthesizedExpression(init))
3468
3652
  init = init.expression;
3469
- if (!init || !ts21.isTemplateExpression(init))
3653
+ if (!init || !ts22.isTemplateExpression(init))
3470
3654
  return null;
3471
3655
  let out = init.head.text;
3472
3656
  for (const span of init.templateSpans) {
3473
- if (!ts21.isIdentifier(span.expression))
3657
+ if (!ts22.isIdentifier(span.expression))
3474
3658
  return null;
3475
3659
  const value = resolved.get(span.expression.text);
3476
3660
  if (value === undefined)
@@ -3501,30 +3685,30 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3501
3685
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
3502
3686
  if (constInfo?.value === undefined)
3503
3687
  return null;
3504
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3688
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3505
3689
  if (sf.statements.length !== 1)
3506
3690
  return null;
3507
3691
  const stmt = sf.statements[0];
3508
- if (!ts21.isExpressionStatement(stmt))
3692
+ if (!ts22.isExpressionStatement(stmt))
3509
3693
  return null;
3510
3694
  let parsed = stmt.expression;
3511
- while (ts21.isParenthesizedExpression(parsed))
3695
+ while (ts22.isParenthesizedExpression(parsed))
3512
3696
  parsed = parsed.expression;
3513
- if (!ts21.isObjectLiteralExpression(parsed))
3697
+ if (!ts22.isObjectLiteralExpression(parsed))
3514
3698
  return null;
3515
3699
  for (const prop of parsed.properties) {
3516
- if (!ts21.isPropertyAssignment(prop))
3700
+ if (!ts22.isPropertyAssignment(prop))
3517
3701
  continue;
3518
3702
  const name = prop.name;
3519
- const propKey = ts21.isIdentifier(name) || ts21.isStringLiteral(name) || ts21.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3703
+ const propKey = ts22.isIdentifier(name) || ts22.isStringLiteral(name) || ts22.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
3520
3704
  if (propKey !== key)
3521
3705
  continue;
3522
3706
  let v = prop.initializer;
3523
- while (ts21.isParenthesizedExpression(v))
3707
+ while (ts22.isParenthesizedExpression(v))
3524
3708
  v = v.expression;
3525
- if (ts21.isNumericLiteral(v))
3709
+ if (ts22.isNumericLiteral(v))
3526
3710
  return { kind: "number", text: v.text };
3527
- if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3711
+ if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3528
3712
  return { kind: "string", text: v.text };
3529
3713
  }
3530
3714
  return null;
@@ -3532,28 +3716,28 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
3532
3716
  return null;
3533
3717
  }
3534
3718
  function evalStringArrayJoin(source) {
3535
- const sf = ts21.createSourceFile("__join.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
3719
+ const sf = ts22.createSourceFile("__join.ts", `const __x = (${source});`, ts22.ScriptTarget.Latest, false);
3536
3720
  const stmt = sf.statements[0];
3537
- if (!stmt || !ts21.isVariableStatement(stmt))
3721
+ if (!stmt || !ts22.isVariableStatement(stmt))
3538
3722
  return null;
3539
3723
  let node = stmt.declarationList.declarations[0]?.initializer;
3540
- while (node && ts21.isParenthesizedExpression(node))
3724
+ while (node && ts22.isParenthesizedExpression(node))
3541
3725
  node = node.expression;
3542
- if (!node || !ts21.isCallExpression(node))
3726
+ if (!node || !ts22.isCallExpression(node))
3543
3727
  return null;
3544
3728
  const callee = node.expression;
3545
- if (!ts21.isPropertyAccessExpression(callee))
3729
+ if (!ts22.isPropertyAccessExpression(callee))
3546
3730
  return null;
3547
3731
  if (callee.name.text !== "join")
3548
3732
  return null;
3549
3733
  let recv = callee.expression;
3550
- while (ts21.isParenthesizedExpression(recv))
3734
+ while (ts22.isParenthesizedExpression(recv))
3551
3735
  recv = recv.expression;
3552
- if (!ts21.isArrayLiteralExpression(recv))
3736
+ if (!ts22.isArrayLiteralExpression(recv))
3553
3737
  return null;
3554
3738
  const parts = [];
3555
3739
  for (const el of recv.elements) {
3556
- if (ts21.isStringLiteral(el) || ts21.isNoSubstitutionTemplateLiteral(el)) {
3740
+ if (ts22.isStringLiteral(el) || ts22.isNoSubstitutionTemplateLiteral(el)) {
3557
3741
  parts.push(el.text);
3558
3742
  } else {
3559
3743
  return null;
@@ -3562,7 +3746,7 @@ function evalStringArrayJoin(source) {
3562
3746
  let sep = ",";
3563
3747
  if (node.arguments.length >= 1) {
3564
3748
  const arg = node.arguments[0];
3565
- if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
3749
+ if (ts22.isStringLiteral(arg) || ts22.isNoSubstitutionTemplateLiteral(arg))
3566
3750
  sep = arg.text;
3567
3751
  else
3568
3752
  return null;
@@ -3570,11 +3754,11 @@ function evalStringArrayJoin(source) {
3570
3754
  return parts.join(sep);
3571
3755
  }
3572
3756
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3573
- if (!ts21.isElementAccessExpression(val))
3757
+ if (!ts22.isElementAccessExpression(val))
3574
3758
  return null;
3575
3759
  const obj = val.expression;
3576
3760
  const arg = val.argumentExpression;
3577
- if (!ts21.isIdentifier(obj) || !ts21.isIdentifier(arg))
3761
+ if (!ts22.isIdentifier(obj) || !ts22.isIdentifier(arg))
3578
3762
  return null;
3579
3763
  let indexPropName;
3580
3764
  let defaultKey;
@@ -3590,35 +3774,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
3590
3774
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
3591
3775
  if (constInfo?.value === undefined)
3592
3776
  return null;
3593
- const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
3777
+ const sf = ts22.createSourceFile("__rec.ts", `(${constInfo.value})`, ts22.ScriptTarget.Latest, true);
3594
3778
  if (sf.statements.length !== 1)
3595
3779
  return null;
3596
3780
  const stmt = sf.statements[0];
3597
- if (!ts21.isExpressionStatement(stmt))
3781
+ if (!ts22.isExpressionStatement(stmt))
3598
3782
  return null;
3599
3783
  let parsed = stmt.expression;
3600
- while (ts21.isParenthesizedExpression(parsed))
3784
+ while (ts22.isParenthesizedExpression(parsed))
3601
3785
  parsed = parsed.expression;
3602
- if (!ts21.isObjectLiteralExpression(parsed))
3786
+ if (!ts22.isObjectLiteralExpression(parsed))
3603
3787
  return null;
3604
3788
  const entries = [];
3605
3789
  for (const prop of parsed.properties) {
3606
- if (!ts21.isPropertyAssignment(prop))
3790
+ if (!ts22.isPropertyAssignment(prop))
3607
3791
  return null;
3608
3792
  let key;
3609
- if (ts21.isIdentifier(prop.name)) {
3793
+ if (ts22.isIdentifier(prop.name)) {
3610
3794
  key = prop.name.text;
3611
- } else if (ts21.isStringLiteral(prop.name) || ts21.isNoSubstitutionTemplateLiteral(prop.name)) {
3795
+ } else if (ts22.isStringLiteral(prop.name) || ts22.isNoSubstitutionTemplateLiteral(prop.name)) {
3612
3796
  key = prop.name.text;
3613
3797
  } else {
3614
3798
  return null;
3615
3799
  }
3616
3800
  let v = prop.initializer;
3617
- while (ts21.isParenthesizedExpression(v))
3801
+ while (ts22.isParenthesizedExpression(v))
3618
3802
  v = v.expression;
3619
- if (ts21.isNumericLiteral(v)) {
3803
+ if (ts22.isNumericLiteral(v)) {
3620
3804
  entries.push({ key, value: { kind: "number", text: v.text } });
3621
- } else if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
3805
+ } else if (ts22.isStringLiteral(v) || ts22.isNoSubstitutionTemplateLiteral(v)) {
3622
3806
  entries.push({ key, value: { kind: "string", text: v.text } });
3623
3807
  } else {
3624
3808
  return null;
@@ -3674,7 +3858,7 @@ function computeSsrSeedPlan(metadata) {
3674
3858
  // ../jsx/src/rich-type-refusal.ts
3675
3859
  var EMPTY_BINDINGS2 = new Map;
3676
3860
  // ../jsx/src/shared-program.ts
3677
- import ts22 from "typescript";
3861
+ import ts24 from "typescript";
3678
3862
  // ../jsx/src/adapters/interface.ts
3679
3863
  class BaseAdapter {
3680
3864
  renderChildren(children) {
@@ -3750,7 +3934,7 @@ class JsxAdapter extends BaseAdapter {
3750
3934
  }
3751
3935
  const rawInitialValue = preserveTypes ? signal.typedInitialValue ?? signal.initialValue : signal.initialValue;
3752
3936
  const initialValue = rawInitialValue.trim().startsWith("{") ? `(${rawInitialValue})` : rawInitialValue;
3753
- const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive";
3937
+ const needsTypeAssertion = preserveTypes && !signal.typedInitialValue && signal.type.kind !== "unknown" && signal.type.kind !== "primitive" && signal.type.raw !== "object";
3754
3938
  if (needsTypeAssertion) {
3755
3939
  lines.push(` const ${signal.getter} = () => ${initialValue} as ${signal.type.raw}`);
3756
3940
  } else {
@@ -3769,12 +3953,16 @@ class JsxAdapter extends BaseAdapter {
3769
3953
  const computation = preserveTypes ? memo.typedComputation ?? memo.computation : memo.computation;
3770
3954
  lines.push(` const ${memo.name} = ${computation}`);
3771
3955
  }
3956
+ const moduleScopeNames = this.moduleScopeDeclarationNames(ir);
3772
3957
  for (const constant of ir.metadata.localConstants) {
3773
3958
  if (constant.isExported)
3774
3959
  continue;
3960
+ if (moduleScopeNames.has(constant.name))
3961
+ continue;
3775
3962
  const keyword = constant.declarationKind ?? "const";
3776
3963
  if (!constant.value) {
3777
- lines.push(` ${keyword} ${constant.name}`);
3964
+ const typeAnnotation = preserveTypes && (constant.typeAnnotation ?? constant.type) ? `: ${constant.typeAnnotation ?? constant.type?.raw}` : "";
3965
+ lines.push(` ${keyword} ${constant.name}${typeAnnotation}`);
3778
3966
  continue;
3779
3967
  }
3780
3968
  const value = constant.value.trim();
@@ -3783,9 +3971,12 @@ class JsxAdapter extends BaseAdapter {
3783
3971
  if (!reachable.has(constant.name))
3784
3972
  continue;
3785
3973
  const constValue = preserveTypes ? constant.typedValue ?? constant.value : constant.value;
3786
- lines.push(` ${keyword} ${constant.name} = ${constValue}`);
3974
+ const letTypeAnnotation = preserveTypes && keyword === "let" && constant.typeAnnotation ? `: ${constant.typeAnnotation}` : "";
3975
+ lines.push(` ${keyword} ${constant.name}${letTypeAnnotation} = ${constValue}`);
3787
3976
  }
3788
3977
  for (const func of localFunctions) {
3978
+ if (moduleScopeNames.has(func.name))
3979
+ continue;
3789
3980
  if (!reachable.has(func.name))
3790
3981
  continue;
3791
3982
  const params = preserveTypes && func.typedParams !== undefined ? func.typedParams : func.params.map(formatParamWithType).join(", ");
@@ -3795,6 +3986,127 @@ class JsxAdapter extends BaseAdapter {
3795
3986
  lines.push(` ${asyncKw}function ${func.name}(${params})${returnAnnotation} ${body}`);
3796
3987
  }
3797
3988
  return lines.join(`
3989
+ `);
3990
+ }
3991
+ moduleScopeNamesCache = new WeakMap;
3992
+ moduleScopeDeclarationNames(ir) {
3993
+ const cached = this.moduleScopeNamesCache.get(ir);
3994
+ if (cached)
3995
+ return cached;
3996
+ const componentScope = new Set;
3997
+ for (const sig of ir.metadata.signals) {
3998
+ if (sig.isModule)
3999
+ continue;
4000
+ componentScope.add(sig.getter);
4001
+ if (sig.setter)
4002
+ componentScope.add(sig.setter);
4003
+ }
4004
+ for (const memo of ir.metadata.memos) {
4005
+ if (!memo.isModule)
4006
+ componentScope.add(memo.name);
4007
+ }
4008
+ for (const p of ir.metadata.propsParams)
4009
+ componentScope.add(p.name);
4010
+ if (ir.metadata.propsObjectName)
4011
+ componentScope.add(ir.metadata.propsObjectName);
4012
+ if (ir.metadata.restPropsName)
4013
+ componentScope.add(ir.metadata.restPropsName);
4014
+ for (const c of ir.metadata.localConstants) {
4015
+ if (!c.isModule)
4016
+ componentScope.add(c.name);
4017
+ }
4018
+ for (const f of ir.metadata.localFunctions) {
4019
+ if (!f.isModule)
4020
+ componentScope.add(f.name);
4021
+ }
4022
+ const exported = new Set;
4023
+ const candidates = new Map;
4024
+ for (const c of ir.metadata.localConstants) {
4025
+ if (!c.isModule)
4026
+ continue;
4027
+ if (c.isJsx || c.isJsxFunction)
4028
+ continue;
4029
+ if (c.isExported) {
4030
+ exported.add(c.name);
4031
+ continue;
4032
+ }
4033
+ candidates.set(c.name, c.freeIdentifiers ?? extractFreeIdentifiersFromText(c.value ?? ""));
4034
+ }
4035
+ for (const f of ir.metadata.localFunctions) {
4036
+ if (!f.isModule)
4037
+ continue;
4038
+ if (f.isJsxFunction || f.isMultiReturnJsxHelper)
4039
+ continue;
4040
+ if (f.isExported) {
4041
+ exported.add(f.name);
4042
+ continue;
4043
+ }
4044
+ const params = f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4045
+ candidates.set(f.name, extractFreeIdentifiersFromText(`(${params}) => ${f.body}`));
4046
+ }
4047
+ const referencesAny = (refs, names) => {
4048
+ for (const ref of refs) {
4049
+ if (names.has(ref))
4050
+ return true;
4051
+ }
4052
+ return false;
4053
+ };
4054
+ let changed = true;
4055
+ while (changed) {
4056
+ changed = false;
4057
+ for (const [name, refs] of candidates) {
4058
+ if (referencesAny(refs, componentScope)) {
4059
+ candidates.delete(name);
4060
+ componentScope.add(name);
4061
+ changed = true;
4062
+ }
4063
+ }
4064
+ }
4065
+ const result = new Set([...exported, ...candidates.keys()]);
4066
+ this.moduleScopeNamesCache.set(ir, result);
4067
+ return result;
4068
+ }
4069
+ generateModuleScopeDeclarations(ir) {
4070
+ const { preserveTypes } = this.jsxConfig;
4071
+ const moduleNames = this.moduleScopeDeclarationNames(ir);
4072
+ const entries = [];
4073
+ for (const t of ir.metadata.typeDefinitions) {
4074
+ entries.push({ line: t.loc.start.line, text: t.definition });
4075
+ }
4076
+ for (const c of ir.metadata.localConstants) {
4077
+ if (!c.isModule || !moduleNames.has(c.name))
4078
+ continue;
4079
+ const keyword = c.declarationKind ?? "const";
4080
+ const exportKw = c.isExported ? "export " : "";
4081
+ if (!c.value) {
4082
+ const typeAnnotation = preserveTypes && (c.typeAnnotation ?? c.type) ? `: ${c.typeAnnotation ?? c.type?.raw}` : "";
4083
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name}${typeAnnotation}` });
4084
+ continue;
4085
+ }
4086
+ const trimmed = c.value.trim();
4087
+ if (/^new WeakMap\b/.test(trimmed))
4088
+ continue;
4089
+ if (c.isExported && /^createContext\b/.test(trimmed))
4090
+ continue;
4091
+ const value = preserveTypes ? c.typedValue ?? c.value : c.value;
4092
+ const letTypeAnnotation = preserveTypes && keyword === "let" && c.typeAnnotation ? `: ${c.typeAnnotation}` : "";
4093
+ entries.push({ line: c.loc.start.line, text: `${exportKw}${keyword} ${c.name}${letTypeAnnotation} = ${value}` });
4094
+ }
4095
+ for (const f of ir.metadata.localFunctions) {
4096
+ if (!f.isModule || !moduleNames.has(f.name))
4097
+ continue;
4098
+ const params = preserveTypes && f.typedParams !== undefined ? f.typedParams : f.params.map(formatParamWithType).join(", ");
4099
+ const returnAnnotation = preserveTypes && f.typedReturnType ? `: ${f.typedReturnType}` : "";
4100
+ const body = preserveTypes ? f.typedBody ?? f.body : f.body;
4101
+ const asyncKw = f.isAsync ? "async " : "";
4102
+ const exportKw = f.isExported ? "export " : "";
4103
+ entries.push({
4104
+ line: f.loc.start.line,
4105
+ text: `${exportKw}${asyncKw}function ${f.name}(${params})${returnAnnotation} ${body}`
4106
+ });
4107
+ }
4108
+ entries.sort((a, b) => a.line - b.line);
4109
+ return entries.map((e) => e.text).join(`
3798
4110
  `);
3799
4111
  }
3800
4112
  renderNodeRaw(node) {
@@ -3806,6 +4118,15 @@ class JsxAdapter extends BaseAdapter {
3806
4118
  }
3807
4119
  return this.renderNode(node);
3808
4120
  }
4121
+ renderTemplatePartsAsJs(parts) {
4122
+ return templatePartsToJsExpr(parts, { typed: this.jsxConfig.preserveTypes });
4123
+ }
4124
+ expressionValueToJs(value) {
4125
+ if (this.jsxConfig.preserveTypes && value.parts && value.expr === templatePartsToJsExpr(value.parts)) {
4126
+ return this.renderTemplatePartsAsJs(value.parts);
4127
+ }
4128
+ return value.expr;
4129
+ }
3809
4130
  renderScopeMarker(instanceIdExpr) {
3810
4131
  return `${BF_SCOPE}={${instanceIdExpr}}`;
3811
4132
  }
@@ -3873,6 +4194,7 @@ class TestAdapter extends JsxAdapter {
3873
4194
  generate(ir) {
3874
4195
  this.componentName = ir.metadata.componentName;
3875
4196
  const imports = this.generateImports(ir);
4197
+ const moduleConstants = this.generateModuleScopeDeclarations(ir);
3876
4198
  const types = this.generateTypes(ir);
3877
4199
  const component = this.generateComponent(ir);
3878
4200
  const defaultExport = ir.metadata.hasDefaultExport ? `
@@ -3881,9 +4203,11 @@ export default ${this.componentName}` : "";
3881
4203
  imports,
3882
4204
  types: types || "",
3883
4205
  component,
3884
- defaultExport
4206
+ defaultExport,
4207
+ moduleConstants,
4208
+ moduleConstantsIncludeExports: true
3885
4209
  };
3886
- const template = [imports, types, component].filter(Boolean).join(`
4210
+ const template = [imports, moduleConstants, types, component].filter(Boolean).join(`
3887
4211
 
3888
4212
  `) + defaultExport;
3889
4213
  return {
@@ -3914,9 +4238,6 @@ export default ${this.componentName}` : "";
3914
4238
  }
3915
4239
  generateTypes(ir) {
3916
4240
  const lines = [];
3917
- for (const typeDef of ir.metadata.typeDefinitions) {
3918
- lines.push(typeDef.definition);
3919
- }
3920
4241
  const propsTypeName = ir.metadata.propsType?.raw;
3921
4242
  if (propsTypeName && !ir.metadata.propsObjectName) {
3922
4243
  lines.push("");
@@ -3939,7 +4260,7 @@ export default ${this.componentName}` : "";
3939
4260
  const bodyRefText = [jsxBody, signalInits, scopeIdLine].join(`
3940
4261
  `);
3941
4262
  const bfScopeAlias = /\b__bfScope\b/.test(bodyRefText) ? "__bfScope" : "__bfScope: _bfScope";
3942
- const propsParams = ir.metadata.propsParams.map((p) => p.defaultValue ? `${p.name} = ${p.defaultValue}` : p.name).join(", ");
4263
+ const propsParams = ir.metadata.propsParams.map((p) => propsDestructureBinding(p)).join(", ");
3943
4264
  const restPropsName = ir.metadata.restPropsName;
3944
4265
  const hydrationProps = `__instanceId, ${bfScopeAlias}`;
3945
4266
  const parts = [];
@@ -4086,13 +4407,7 @@ export default ${this.componentName}` : "";
4086
4407
  }
4087
4408
  flattenTemplate(value) {
4088
4409
  const v = value;
4089
- return "`" + v.parts.map((p) => {
4090
- if (p.type === "string")
4091
- return p.value;
4092
- if (p.type === "ternary")
4093
- return `\${${p.condition} ? '${p.whenTrue}' : '${p.whenFalse}'}`;
4094
- return `\${(${JSON.stringify(p.cases)})[${p.key}]}`;
4095
- }).join("") + "`";
4410
+ return this.renderTemplatePartsAsJs(v.parts);
4096
4411
  }
4097
4412
  renderComponentProps(comp) {
4098
4413
  const parts = [];
@@ -4587,7 +4902,7 @@ function dangerousInnerHtmlDiagnostic(expr, loc, reason) {
4587
4902
  };
4588
4903
  }
4589
4904
  // ../jsx/src/combine-client-js.ts
4590
- import ts23 from "typescript";
4905
+ import ts25 from "typescript";
4591
4906
  // ../jsx/src/loop-destructure.ts
4592
4907
  function isLowerableLoopDestructure(loop) {
4593
4908
  const bindings = loop.paramBindings;
@@ -4727,9 +5042,9 @@ function escapeRe(s) {
4727
5042
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4728
5043
  }
4729
5044
  // ../jsx/src/debug.ts
4730
- import ts24 from "typescript";
5045
+ import ts26 from "typescript";
4731
5046
  // ../jsx/src/profiler.ts
4732
- import ts25 from "typescript";
5047
+ import ts27 from "typescript";
4733
5048
 
4734
5049
  // ../jsx/src/index.ts
4735
5050
  registerBuiltinLoweringPlugins();
@@ -5555,7 +5870,7 @@ function collectImportedLoopChildComponentErrors(ir, componentName) {
5555
5870
  }
5556
5871
 
5557
5872
  // src/adapter/spread/spread-codegen.ts
5558
- import ts26 from "typescript";
5873
+ import ts28 from "typescript";
5559
5874
  function conditionalSpreadToPerl(ctx, expr) {
5560
5875
  if (!expr || expr.kind !== "conditional")
5561
5876
  return null;
@@ -5610,7 +5925,7 @@ function recordIndexAccessToPerl(ctx, val) {
5610
5925
  if (val.kind !== "index-access" || val.object.kind !== "identifier" || val.index.kind !== "identifier") {
5611
5926
  return null;
5612
5927
  }
5613
- const tsVal = ts26.factory.createElementAccessExpression(ts26.factory.createIdentifier(val.object.name), ts26.factory.createIdentifier(val.index.name));
5928
+ const tsVal = ts28.factory.createElementAccessExpression(ts28.factory.createIdentifier(val.object.name), ts28.factory.createIdentifier(val.index.name));
5614
5929
  const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants, ctx.propsParams);
5615
5930
  if (!parsed)
5616
5931
  return null;
@@ -1,5 +1,5 @@
1
1
  package BarefootJS::Backend::Mojo;
2
- our $VERSION = "0.31.0";
2
+ our $VERSION = "0.31.2";
3
3
  use Mojo::Base -base, -signatures;
4
4
 
5
5
  use Mojo::ByteStream qw(b);
@@ -1,5 +1,5 @@
1
1
  package Mojolicious::Plugin::BarefootJS::DevReload;
2
- our $VERSION = "0.31.0";
2
+ our $VERSION = "0.31.2";
3
3
  use Mojo::Base 'Mojolicious::Plugin', -signatures;
4
4
 
5
5
  =head1 NAME
@@ -1,5 +1,5 @@
1
1
  package Mojolicious::Plugin::BarefootJS;
2
- our $VERSION = "0.31.0";
2
+ our $VERSION = "0.31.2";
3
3
  use Mojo::Base 'Mojolicious::Plugin', -signatures;
4
4
 
5
5
  use Mojo::File qw(path);
@@ -110,15 +110,41 @@ sub register ($self, $app, $config = {}) {
110
110
  $bf->_scope_id($template . '_' . substr(rand() =~ s/^0\.//r, 0, 6));
111
111
  $bf->register_components_from_manifest($m);
112
112
 
113
- # Seed each ssrDefault into the stash unless the caller has
114
- # already supplied a value for that key — callers always win.
113
+ # Seed each ssrDefault into the stash unless the caller has already
114
+ # supplied a value for that key — callers always win. Resolved
115
+ # through the production `BarefootJS::_derive_stash_from_defaults`
116
+ # (the same call `register_components_from_manifest` above makes for
117
+ # child components) so an aliased destructured prop's CALLER-facing
118
+ # key (`propName`, e.g. `n` for `{ n: count }`) is honoured — the
119
+ # caller stash IS the props document for a top-level render (a route
120
+ # handler sets `$c->stash(n => 5)` before `$c->render(...)`).
121
+ # Resolving `$d->{value}` directly, ignoring `propName`, was the
122
+ # #2524 production bug: it could never see a caller-supplied `n`,
123
+ # only ever the static default.
124
+ #
125
+ # CONSTRAINT (documented, not fixed): because the caller stash IS
126
+ # the props document here, `propName` resolution also sees every
127
+ # key Mojolicious itself already populates on `$c->stash` before
128
+ # this hook runs — `action`, `controller`, `template`,
129
+ # `template_class`, and friends (Mojolicious::Controller's own
130
+ # reserved stash keys). A ROOT-level component whose destructured
131
+ # prop happens to alias FROM one of those names (e.g. `{ action:
132
+ # label }`) resolves `propName => 'action'` against Mojo's OWN
133
+ # internal value, not a genuinely absent prop — the component
134
+ # silently picks up the current route's action name instead of its
135
+ # static default. This can't collide for a CHILD render
136
+ # (`register_components_from_manifest`'s renderer closure passes a
137
+ # fresh, purpose-built props hash, never the live `$c->stash`), only
138
+ # for a root-level render where the stash doubles as both Mojo's
139
+ # own bookkeeping and the props document. Avoid destructuring a prop
140
+ # named `action`/`controller`/`template`/`template_class`/etc. on a
141
+ # ROOT component if this matters to you.
115
142
  my $defaults = $entry->{ssrDefaults};
116
143
  if (ref($defaults) eq 'HASH') {
117
- for my $name (keys %$defaults) {
144
+ my %extra = BarefootJS::_derive_stash_from_defaults($defaults, $c->stash);
145
+ for my $name (keys %extra) {
118
146
  next if exists $c->stash->{$name};
119
- my $d = $defaults->{$name};
120
- my $value = ref($d) eq 'HASH' ? $d->{value} : $d;
121
- $c->stash->{$name} = $value;
147
+ $c->stash->{$name} = $extra{$name};
122
148
  }
123
149
  }
124
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/mojolicious",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
4
4
  "description": "Mojolicious EP template adapter for BarefootJS - generates .html.ep files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "directory": "packages/adapter-mojolicious"
53
53
  },
54
54
  "dependencies": {
55
- "@barefootjs/shared": "0.31.1"
55
+ "@barefootjs/shared": "0.31.3"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@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
  "vite": "^6.0.0"
77
77
  }
78
78
  }
@@ -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
  }
@@ -5,8 +5,8 @@
5
5
  * Used by adapter-tests conformance runner.
6
6
  */
7
7
 
8
- import { compileJSX, extractSsrDefaults, augmentInheritedPropAccesses, importsSearchParams, evaluateSignalInit, tryEvaluateSignalInit } from '@barefootjs/jsx'
9
- import type { ComponentIR } from '@barefootjs/jsx'
8
+ import { compileJSX, extractSsrDefaults, deriveStashFromDefaults, augmentInheritedPropAccesses, importsSearchParams, evaluateSignalInit } from '@barefootjs/jsx'
9
+ import type { ComponentIR, SsrDefault } from '@barefootjs/jsx'
10
10
  import { mkdir, rm } from 'node:fs/promises'
11
11
  import { resolve } from 'node:path'
12
12
 
@@ -376,8 +376,10 @@ function buildChildRenderers(
376
376
  // (#1897) Route non-param props into the rest bag (JSX rest
377
377
  // semantics): a caller prop the child didn't destructure belongs
378
378
  // in the bag, not as a top-level stash var. Mirrors the Xslate
379
- // harness and the production isRestProps branch.
380
- const paramNames = (childIR.metadata.propsParams ?? []).map(p => p.name)
379
+ // harness and the production isRestProps branch. Keep-set uses
380
+ // CALLER-facing names (`sourceName ?? name`) `$child_props` is
381
+ // keyed by whatever the calling template passed (#2524).
382
+ const paramNames = (childIR.metadata.propsParams ?? []).map(p => p.sourceName ?? p.name)
381
383
  const keepList = [...new Set([...paramNames, rest, 'children', 'key', '_bf_slot'])]
382
384
  .map(n => `'${n.replace(/[\\']/g, m => `\\${m}`)}'`)
383
385
  .join(', ')
@@ -408,9 +410,16 @@ function buildChildRenderers(
408
410
  lines.push(` } else {`)
409
411
  lines.push(` $child_bf->_scope_id('${componentName}_' . substr(rand() =~ s/^0\\.//r, 0, 6));`)
410
412
  lines.push(` }`)
411
- // Seed statically-derived defaults under the caller's props (#1897)
412
- // so undeclared optional props / signals don't abort strict vars.
413
- lines.push(` my $rendered = $child_mt->render($child_tmpl, { %$defaults_${snakeName}, %$child_props, bf => $child_bf });`)
413
+ // Seed statically-derived defaults under the caller's props (#1897) so
414
+ // undeclared optional props / signals don't abort strict vars — through
415
+ // the production `BarefootJS::_derive_stash_from_defaults`, which
416
+ // resolves each entry's `propName` against the REAL `$child_props`
417
+ // (falling back to the static `value`) instead of a flat merge that
418
+ // never resolves an aliased destructured prop's CALLER-facing key (`n`)
419
+ // onto its local template var (`count`) (#2524 SSR half). Caller props
420
+ // still win overall via the final merge.
421
+ lines.push(` my %extra_${snakeName} = BarefootJS::_derive_stash_from_defaults($defaults_${snakeName}, $child_props);`)
422
+ lines.push(` my $rendered = $child_mt->render($child_tmpl, { %$child_props, %extra_${snakeName}, bf => $child_bf });`)
414
423
  lines.push(` die $rendered->to_string if ref $rendered;`)
415
424
  lines.push(` chomp $rendered;`)
416
425
  lines.push(` return $rendered;`)
@@ -441,10 +450,14 @@ function toSnakeCase(name: string): string {
441
450
  * `props.<x>` accesses, signal initial values, and memo ssrDefaults.
442
451
  * Without these, a child template referencing an optional prop the
443
452
  * caller didn't pass (`$id`, `$className`) or its own signal (`$open`)
444
- * aborts under Mojo::Template's strict vars. Caller-passed props win at
445
- * merge time (`{ %$defaults, %$child_props }`). Mirrors the root-side
446
- * seeding in `buildPerlProps` and the production plugin's
447
- * `ssrDefaults` consumption.
453
+ * aborts under Mojo::Template's strict vars. Caller-passed props are
454
+ * resolved at merge time through `BarefootJS::_derive_stash_from_defaults`
455
+ * (see `buildChildRenderers`), not a flat merge — declared-prop entries
456
+ * below are therefore emitted VERBATIM (`{value, propName?}`, from
457
+ * `extractSsrDefaults`, not flattened) so that resolution has an aliased
458
+ * prop's CALLER-facing key to read (#2524 SSR half). Mirrors the root-side
459
+ * seeding in `buildPerlProps` and the production plugin's `ssrDefaults`
460
+ * consumption.
448
461
  */
449
462
  function buildChildDefaultsPerl(ir: ComponentIR): string {
450
463
  // Surface inherited `props.<x>` reads hidden inside template-literal
@@ -454,16 +467,12 @@ function buildChildDefaultsPerl(ir: ComponentIR): string {
454
467
  augmentInheritedPropAccesses(ir)
455
468
  const entries: string[] = []
456
469
  const declared = new Set<string>()
470
+ const ssrDefaults = extractSsrDefaults(ir.metadata) ?? {}
457
471
  for (const param of ir.metadata.propsParams) {
472
+ if (param.isRest) continue
458
473
  declared.add(param.name)
459
- if (param.defaultValue) {
460
- const result = tryEvaluateSignalInit(param.defaultValue.trim())
461
- if (result.ok) {
462
- entries.push(`${param.name} => ${toPerlLiteral(result.value)}`)
463
- continue
464
- }
465
- }
466
- entries.push(`${param.name} => undef`)
474
+ const d = ssrDefaults[param.name]
475
+ entries.push(`${param.name} => ${d ? ssrDefaultEntryToPerl(d) : 'undef'}`)
467
476
  }
468
477
  if (ir.metadata.propsObjectName) {
469
478
  for (const name of collectPropsObjectAccesses(ir, ir.metadata.propsObjectName)) {
@@ -472,16 +481,33 @@ function buildChildDefaultsPerl(ir: ComponentIR): string {
472
481
  entries.push(`${name} => undef`)
473
482
  }
474
483
  }
484
+ // Signal / memo entries are emitted as BARE Perl values (not the
485
+ // `{value=>..., propName=>...}` hashref shape `ssrDefaultEntryToPerl`
486
+ // wraps declared-prop entries in). Under the merge-order flip
487
+ // (`buildChildRenderers`: `{ %$child_props, %extra, ... }`, extra applied
488
+ // LAST — see its docstring), a bare entry now wins over a same-named
489
+ // caller prop, same as it would over an un-mangled caller prop before the
490
+ // flip it lost to. This is NOT a regression to fix: `_derive_stash_from_
491
+ // defaults`'s non-hashref branch (`$extra{$name} = $d`, unconditional)
492
+ // and its hashref-with-no-`propName` branch (`else { $extra{$name} =
493
+ // $d->{value} }`) are BEHAVIORALLY IDENTICAL — both always use the
494
+ // static value, ignoring `$props` entirely — so wrapping these in
495
+ // `{value=>...}` would change nothing. A propName-less entry (signal /
496
+ // memo local) is BY DESIGN never sourced from props in ANY of the ported
497
+ // runtimes (see `ssr-defaults.ts`'s `deriveStashFromDefaults` docstring:
498
+ // "the caller cannot override them by construction") — the Jinja/Twig
499
+ // harnesses' own signal/memo entries (`{value: X}`, no `propName`) clobber
500
+ // a same-named caller prop the exact same way under their own extras-win
501
+ // merge. Mojolicious's bare-entry shape is a representation choice, not a
502
+ // semantic divergence from those adapters.
475
503
  for (const signal of ir.metadata.signals) {
476
504
  if (signal.envReader) continue // env signal is the request reader, not a stashed value (#2057)
477
505
  const value = evaluateSignalInit(signal.initialValue.trim(), undefined)
478
506
  entries.push(`${signal.getter} => ${value !== null ? toPerlLiteral(value) : 'undef'}`)
479
507
  }
480
- const ssrDefaults = extractSsrDefaults(ir.metadata) ?? {}
481
508
  for (const memo of ir.metadata.memos) {
482
509
  const entry = ssrDefaults[memo.name]
483
- const value =
484
- entry && typeof entry === 'object' && 'value' in entry ? entry.value : undefined
510
+ const value = entry ? entry.value : undefined
485
511
  entries.push(
486
512
  `${memo.name} => ${value !== undefined && value !== null ? toPerlLiteral(value) : 'undef'}`,
487
513
  )
@@ -503,16 +529,18 @@ function buildPerlProps(
503
529
  const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
504
530
  entries.push(`scope_id => '${escapePerlSingleQuoted(explicitScope)}'`)
505
531
 
506
- // Add props params with defaults (before signals, so signals can reference them)
532
+ // Add props params with defaults (before signals, so signals can reference
533
+ // them). Seeded through the shared `deriveStashFromDefaults` (the TS twin
534
+ // of the production `BarefootJS::_derive_stash_from_defaults` this harness
535
+ // ALSO calls at render time for child components) so an aliased
536
+ // destructured prop's CALLER-facing key (`propName`, e.g. `n` for
537
+ // `{ n: count }`) is honoured, not just the local template var name
538
+ // (#2524 SSR half). `props` is keyed by the caller-facing name, exactly
539
+ // what `propName` resolves against.
540
+ const rootSsrDefaults = extractSsrDefaults(ir.metadata) ?? {}
541
+ const derivedProps = deriveStashFromDefaults(rootSsrDefaults, props ?? {})
507
542
  for (const param of ir.metadata.propsParams) {
508
- if (props && param.name in props) continue
509
- if (param.defaultValue) {
510
- const result = tryEvaluateSignalInit(param.defaultValue.trim(), props)
511
- if (result.ok) {
512
- entries.push(`${param.name} => ${toPerlLiteral(result.value)}`)
513
- continue
514
- }
515
- }
543
+ if (param.isRest) continue
516
544
  // No default and no caller-supplied value: pass `undef` so the
517
545
  // Mojo::Template `vars => 1` auto-declaration fires. Without
518
546
  // this, references to an optional prop variable (`$label`,
@@ -523,7 +551,8 @@ function buildPerlProps(
523
551
  // Surfaces with #1443: lowering `[a, b].filter(Boolean).join(' ')`
524
552
  // emits a literal `$label` reference where the BF101 path used
525
553
  // to emit `''`, exposing this latent test-harness gap.
526
- entries.push(`${param.name} => undef`)
554
+ const value = derivedProps[param.name]
555
+ entries.push(`${param.name} => ${value !== undefined && value !== null ? toPerlLiteral(value) : 'undef'}`)
527
556
  }
528
557
 
529
558
  // (#checkbox) SolidJS props-object pattern: `function Checkbox(props:
@@ -554,7 +583,11 @@ function buildPerlProps(
554
583
  // rather than silently dropping it into an unused `my $placeholder`. (#1467
555
584
  // Phase 2b — mirrors the Go harness fix in the sibling `test-render.ts`.)
556
585
  const restPropsName = ir.metadata.restPropsName
557
- const declaredParams = new Set(ir.metadata.propsParams.map(p => p.name))
586
+ // Caller-facing keys an aliased destructured prop's DECLARED set for
587
+ // rest-bag routing must match what the caller actually sent (`n`), not
588
+ // the local binding (`count`), or the caller's own prop silently gets
589
+ // swept into the rest bag as an undeclared extra (#2524).
590
+ const declaredParams = new Set(ir.metadata.propsParams.map(p => p.sourceName ?? p.name))
558
591
  const restBagEntries: Array<[string, unknown]> = []
559
592
  if (restPropsName && props) {
560
593
  for (const [key, value] of Object.entries(props)) {
@@ -579,10 +612,19 @@ function buildPerlProps(
579
612
  entries.push(`${restPropsName} => ${toPerlLiteral(Object.fromEntries(restBagEntries))}`)
580
613
  }
581
614
 
582
- // Add user props
615
+ // Add user props. Skip a key that's already a declared param's LOCAL
616
+ // template var name — `derivedProps` above already resolved that var
617
+ // correctly (through `propName`, for an aliased prop); re-pushing the raw
618
+ // `props[key]` here would silently clobber it with an UNRELATED value
619
+ // whenever a caller happens to also pass a same-spelled-as-local-name prop
620
+ // that isn't this param's actual `propName` (#2524 — surfaced by the
621
+ // aliased-destructured-prop generated data points, which pass both `n`
622
+ // (the real propName) and an incidental `count` key).
623
+ const localParamNames = new Set(ir.metadata.propsParams.map(p => p.name))
583
624
  if (props) {
584
625
  for (const [key, value] of Object.entries(props)) {
585
626
  if (routedKeys.has(key)) continue
627
+ if (localParamNames.has(key)) continue
586
628
  if (value === null) {
587
629
  // An explicit-null prop must still DECLARE the template var —
588
630
  // `typeof null === 'object'` fails every branch below, so the key
@@ -639,10 +681,9 @@ function buildPerlProps(
639
681
  // computation. Mirror that here so the test harness doesn't diverge
640
682
  // from the plugin: hard-coding `0` masked memos with non-zero
641
683
  // initial values until #1423 added a fixture that exposed the gap.
642
- const ssrDefaults = extractSsrDefaults(ir.metadata) ?? {}
643
684
  for (const memo of ir.metadata.memos) {
644
- const entry = ssrDefaults[memo.name]
645
- const value = entry && typeof entry === 'object' && 'value' in entry ? entry.value : 0
685
+ const entry = rootSsrDefaults[memo.name]
686
+ const value = entry ? entry.value : 0
646
687
  entries.push(`${memo.name} => ${toPerlLiteral(value ?? 0)}`)
647
688
  }
648
689
 
@@ -702,6 +743,21 @@ function perlSingleQuote(s: string): string {
702
743
  return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
703
744
  }
704
745
 
746
+ /**
747
+ * Serialise a single `SsrDefault` entry to a Perl hashref literal, VERBATIM
748
+ * — `value` / `propName` / `isRestProps` intact, exactly the shape
749
+ * `BarefootJS::_derive_stash_from_defaults` expects. Do NOT flatten to a
750
+ * bare `value` here: that was the #2524 SSR-half bug — a flattened entry
751
+ * has nothing left for the propName-aware resolution to read, so an aliased
752
+ * destructured prop's caller-facing key is silently dropped.
753
+ */
754
+ function ssrDefaultEntryToPerl(d: SsrDefault): string {
755
+ const parts: string[] = [`value => ${toPerlLiteral(d.value)}`]
756
+ if (d.propName !== undefined) parts.push(`propName => ${perlSingleQuote(d.propName)}`)
757
+ if (d.isRestProps) parts.push(`isRestProps => 1`)
758
+ return `{${parts.join(', ')}}`
759
+ }
760
+
705
761
  function toPerlLiteral(value: unknown): string {
706
762
  if (typeof value === 'string') return perlSingleQuote(value)
707
763
  if (typeof value === 'number') return String(value)