@barefootjs/xslate 0.31.9 → 0.31.10

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.
@@ -188198,8 +188198,12 @@ function generateDerivedMemoSeed(ctx, ir) {
188198
188198
  const kolon = ctx.convertExpressionToKolon(step.expr, step.parsed);
188199
188199
  if (kolon === "" || !/\$[A-Za-z_]\w*/.test(kolon))
188200
188200
  continue;
188201
- if (new RegExp(`\\$${step.name}\\b`).test(kolon))
188201
+ if (new RegExp(`\\$${step.name}\\b`).test(kolon)) {
188202
+ const tmp = `__bf_seed_${step.name}`;
188203
+ lines.push(`: my $${tmp} = ${kolon};`);
188204
+ lines.push(`: my $${step.name} = $${tmp};`);
188202
188205
  continue;
188206
+ }
188203
188207
  lines.push(`: my $${step.name} = ${kolon};`);
188204
188208
  }
188205
188209
  return lines.length > 0 ? lines.join(`
@@ -26,13 +26,19 @@ export declare function generateContextConsumerSeed(ir: ComponentIR): string;
26
26
  * backend-specific emit guards: skip an empty lowering, skip a lowering that
27
27
  * references no `$var` at all (a constant init/body — e.g. a `derived` step
28
28
  * with empty `frees` — keeps the existing static ssr-defaults seed instead),
29
- * and skip a self-referencing lowering (Kolon's `my` shadows the RHS, so
30
- * `: my $x = … $x …` would read the just-declared undefined lexical rather
31
- * than the render var — the plan rules out SOURCE-level self-refs, but a
32
- * lowered canonical name could still collide, so this stays as the cheap
33
- * defense it always was). `env-reader` and `opaque` steps emit nothing (the
34
- * runtime supplies the reader, or the adapter's ssr-defaults path already
35
- * covers it). (#1297, #2075)
29
+ * and capture-before-shadow a self-referencing lowering (Kolon's `my`
30
+ * shadows the RHS, so `: my $x = … $x …` would read the just-declared
31
+ * undefined lexical rather than the render var — the plan rules out
32
+ * SOURCE-level self-refs, but a lowered canonical name could still collide,
33
+ * e.g. `createMemo(() => props.size ?? 'icon')` lowers `props.size` straight
34
+ * to the stash var `$size`, colliding with the memo's own name). The
35
+ * self-referencing case still emits the SAME verbatim Kolon — nothing about
36
+ * `kolon` changes — just as the RHS of a throwaway `__bf_seed_*` local
37
+ * declared BEFORE `$<name>` is shadowed, so `$<name>` inside `kolon` still
38
+ * resolves to the stash var, not the fresh lexical; a second statement then
39
+ * binds the real name off the already-evaluated capture. `env-reader` and
40
+ * `opaque` steps emit nothing (the runtime supplies the reader, or the
41
+ * adapter's ssr-defaults path already covers it). (#1297, #2075, #2679)
36
42
  */
37
43
  export declare function generateDerivedMemoSeed(ctx: XslateMemoContext, ir: ComponentIR): string;
38
44
  //# sourceMappingURL=seed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/adapter/memo/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EAGrB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAM9D;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAWnE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,WAAW,GAAG,MAAM,CAcvF"}
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/adapter/memo/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EAGrB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAM9D;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAWnE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,WAAW,GAAG,MAAM,CA4BvF"}
package/dist/index.js CHANGED
@@ -188198,8 +188198,12 @@ function generateDerivedMemoSeed(ctx, ir) {
188198
188198
  const kolon = ctx.convertExpressionToKolon(step.expr, step.parsed);
188199
188199
  if (kolon === "" || !/\$[A-Za-z_]\w*/.test(kolon))
188200
188200
  continue;
188201
- if (new RegExp(`\\$${step.name}\\b`).test(kolon))
188201
+ if (new RegExp(`\\$${step.name}\\b`).test(kolon)) {
188202
+ const tmp = `__bf_seed_${step.name}`;
188203
+ lines.push(`: my $${tmp} = ${kolon};`);
188204
+ lines.push(`: my $${step.name} = $${tmp};`);
188202
188205
  continue;
188206
+ }
188203
188207
  lines.push(`: my $${step.name} = ${kolon};`);
188204
188208
  }
188205
188209
  return lines.length > 0 ? lines.join(`
@@ -1 +1 @@
1
- {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,eAAO,MAAM,iBAAiB,EAAE,iBAC/B,CAAA"}
1
+ {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AASxD,eAAO,MAAM,iBAAiB,EAAE,iBAAsB,CAAA"}
package/dist/vite.js CHANGED
@@ -3916,6 +3916,38 @@ function classify(name, origin, expr, parsed, available) {
3916
3916
  }
3917
3917
  return { kind: "derived", name, origin, expr, parsed, frees: [...frees] };
3918
3918
  }
3919
+ function localConstExprsByName(metadata) {
3920
+ const out = new Map;
3921
+ for (const c of metadata.localConstants ?? []) {
3922
+ if (c.isModule || c.declarationKind !== "const" || c.value === undefined)
3923
+ continue;
3924
+ out.set(c.name, c.parsed ?? parseExpression(c.value.trim()));
3925
+ }
3926
+ return out;
3927
+ }
3928
+ function resolveThroughLocalConsts(parsed, localConsts) {
3929
+ let current = parsed;
3930
+ const maxIter = localConsts.size + 1;
3931
+ for (let i = 0;i < maxIter; i++) {
3932
+ const frees = freeIdentifiers(current);
3933
+ if (frees === null)
3934
+ break;
3935
+ let changed = false;
3936
+ for (const name of frees) {
3937
+ const value = localConsts.get(name);
3938
+ if (!value)
3939
+ continue;
3940
+ const inlined = inlineBinding(current, name, value);
3941
+ if (inlined === null)
3942
+ continue;
3943
+ current = inlined;
3944
+ changed = true;
3945
+ }
3946
+ if (!changed)
3947
+ break;
3948
+ }
3949
+ return current;
3950
+ }
3919
3951
  function computeSsrSeedPlan(metadata) {
3920
3952
  const baseScope = metadata.propsParams.map((p) => p.name);
3921
3953
  if (metadata.propsObjectName)
@@ -3924,6 +3956,7 @@ function computeSsrSeedPlan(metadata) {
3924
3956
  baseScope.push(name);
3925
3957
  }
3926
3958
  const available = new Set(baseScope);
3959
+ const localConsts = localConstExprsByName(metadata);
3927
3960
  const steps = [];
3928
3961
  for (const signal of metadata.signals) {
3929
3962
  if (signal.envReader) {
@@ -3935,13 +3968,13 @@ function computeSsrSeedPlan(metadata) {
3935
3968
  }
3936
3969
  }
3937
3970
  const expr = signal.initialValue.trim();
3938
- steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify(signal.getter, "signal", expr, parseExpression(expr), available));
3971
+ steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify(signal.getter, "signal", expr, resolveThroughLocalConsts(parseExpression(expr), localConsts), available));
3939
3972
  available.add(signal.getter);
3940
3973
  }
3941
3974
  for (const memo of metadata.memos) {
3942
3975
  const body = extractArrowBodyExpression(memo.computation);
3943
3976
  const expr = body?.trim() ?? "";
3944
- steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify(memo.name, "memo", expr, memo.parsed ?? parseExpression(expr), available));
3977
+ steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify(memo.name, "memo", expr, resolveThroughLocalConsts(memo.parsed ?? parseExpression(expr), localConsts), available));
3945
3978
  available.add(memo.name);
3946
3979
  }
3947
3980
  return { baseScope, steps };
@@ -6004,8 +6037,12 @@ function generateDerivedMemoSeed(ctx, ir) {
6004
6037
  const kolon = ctx.convertExpressionToKolon(step.expr, step.parsed);
6005
6038
  if (kolon === "" || !/\$[A-Za-z_]\w*/.test(kolon))
6006
6039
  continue;
6007
- if (new RegExp(`\\$${step.name}\\b`).test(kolon))
6040
+ if (new RegExp(`\\$${step.name}\\b`).test(kolon)) {
6041
+ const tmp = `__bf_seed_${step.name}`;
6042
+ lines.push(`: my $${tmp} = ${kolon};`);
6043
+ lines.push(`: my $${step.name} = $${tmp};`);
6008
6044
  continue;
6045
+ }
6009
6046
  lines.push(`: my $${step.name} = ${kolon};`);
6010
6047
  }
6011
6048
  return lines.length > 0 ? lines.join(`
@@ -1,5 +1,5 @@
1
1
  package BarefootJS::Backend::Xslate;
2
- our $VERSION = "0.31.8";
2
+ our $VERSION = "0.31.9";
3
3
  use strict;
4
4
  use warnings;
5
5
  use utf8;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/xslate",
3
- "version": "0.31.9",
3
+ "version": "0.31.10",
4
4
  "description": "Text::Xslate (Kolon) adapter for BarefootJS — compiles IR to .tx templates and ships the Xslate rendering backend; runs under any PSGI/Plack app",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "directory": "packages/adapter-xslate"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.31.9"
58
+ "@barefootjs/shared": "0.31.10"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0",
@@ -72,9 +72,9 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@barefootjs/adapter-tests": "0.1.0",
75
- "@barefootjs/jsx": "0.31.9",
76
- "@barefootjs/vite": "0.31.9",
77
- "@barefootjs/client": "0.31.9",
75
+ "@barefootjs/jsx": "0.31.10",
76
+ "@barefootjs/vite": "0.31.10",
77
+ "@barefootjs/client": "0.31.10",
78
78
  "typescript": "^5.0.0",
79
79
  "vite": "^6.0.0"
80
80
  }
@@ -199,9 +199,11 @@ export function Item(props: { defaultOn?: boolean }) {
199
199
  expect(template).toContain(': my $on = ($defaultOn // 0);')
200
200
  })
201
201
 
202
- // Kolon can't `: my $x = … $x …`; a same-name signal stays on the existing
203
- // (harness/manifest) seeding rather than an in-template seed.
204
- test('does NOT in-template-seed a same-name signal', () => {
202
+ // Kolon can't `: my $x = … $x …` directly (the declaration shadows the
203
+ // RHS's own reference), so a same-name signal captures the lowered
204
+ // expression into a throwaway `__bf_seed_<name>` local BEFORE `$x` is
205
+ // declared, then binds `$x` off that capture (#2679).
206
+ test('in-template-seeds a same-name signal via capture-before-shadow', () => {
205
207
  const { template } = compileAndGenerate(`
206
208
  'use client'
207
209
  import { createSignal } from '@barefootjs/client'
@@ -210,7 +212,8 @@ export function C(props: { x?: number }) {
210
212
  return <span>{x()}</span>
211
213
  }
212
214
  `)
213
- expect(template).not.toContain(': my $x =')
215
+ expect(template).toContain(': my $__bf_seed_x = ($x // 7);')
216
+ expect(template).toContain(': my $x = $__bf_seed_x;')
214
217
  })
215
218
 
216
219
  test('emits data_key_attr on the component root', () => {
@@ -53,13 +53,19 @@ export function generateContextConsumerSeed(ir: ComponentIR): string {
53
53
  * backend-specific emit guards: skip an empty lowering, skip a lowering that
54
54
  * references no `$var` at all (a constant init/body — e.g. a `derived` step
55
55
  * with empty `frees` — keeps the existing static ssr-defaults seed instead),
56
- * and skip a self-referencing lowering (Kolon's `my` shadows the RHS, so
57
- * `: my $x = … $x …` would read the just-declared undefined lexical rather
58
- * than the render var — the plan rules out SOURCE-level self-refs, but a
59
- * lowered canonical name could still collide, so this stays as the cheap
60
- * defense it always was). `env-reader` and `opaque` steps emit nothing (the
61
- * runtime supplies the reader, or the adapter's ssr-defaults path already
62
- * covers it). (#1297, #2075)
56
+ * and capture-before-shadow a self-referencing lowering (Kolon's `my`
57
+ * shadows the RHS, so `: my $x = … $x …` would read the just-declared
58
+ * undefined lexical rather than the render var — the plan rules out
59
+ * SOURCE-level self-refs, but a lowered canonical name could still collide,
60
+ * e.g. `createMemo(() => props.size ?? 'icon')` lowers `props.size` straight
61
+ * to the stash var `$size`, colliding with the memo's own name). The
62
+ * self-referencing case still emits the SAME verbatim Kolon — nothing about
63
+ * `kolon` changes — just as the RHS of a throwaway `__bf_seed_*` local
64
+ * declared BEFORE `$<name>` is shadowed, so `$<name>` inside `kolon` still
65
+ * resolves to the stash var, not the fresh lexical; a second statement then
66
+ * binds the real name off the already-evaluated capture. `env-reader` and
67
+ * `opaque` steps emit nothing (the runtime supplies the reader, or the
68
+ * adapter's ssr-defaults path already covers it). (#1297, #2075, #2679)
63
69
  */
64
70
  export function generateDerivedMemoSeed(ctx: XslateMemoContext, ir: ComponentIR): string {
65
71
  // Package G attached this to metadata at compile time; the `??` fallback
@@ -71,7 +77,21 @@ export function generateDerivedMemoSeed(ctx: XslateMemoContext, ir: ComponentIR)
71
77
  if (step.kind !== 'derived') continue
72
78
  const kolon = ctx.convertExpressionToKolon(step.expr, step.parsed)
73
79
  if (kolon === '' || !/\$[A-Za-z_]\w*/.test(kolon)) continue
74
- if (new RegExp(`\\$${step.name}\\b`).test(kolon)) continue
80
+ if (new RegExp(`\\$${step.name}\\b`).test(kolon)) {
81
+ // Self-referencing lowering (#2679): capture the RHS into a
82
+ // throwaway local BEFORE `$<name>` is declared — `$<name>` inside
83
+ // `kolon` still reads the stash var at this point, nothing is
84
+ // shadowed yet — then bind the real name off the capture. The
85
+ // `__bf_seed_` prefix follows the adapter's established internal-
86
+ // temporary convention (`__bf_item` / `__bf_pair` in the loop
87
+ // lowering); suffixing with `step.name` keeps two derived steps in
88
+ // the same template from colliding, since `step.name` is itself
89
+ // unique per plan (each step declares a distinct local).
90
+ const tmp = `__bf_seed_${step.name}`
91
+ lines.push(`: my $${tmp} = ${kolon};`)
92
+ lines.push(`: my $${step.name} = $${tmp};`)
93
+ continue
94
+ }
75
95
  lines.push(`: my $${step.name} = ${kolon};`)
76
96
  }
77
97
  return lines.length > 0 ? lines.join('\n') + '\n' : ''
@@ -9,5 +9,11 @@
9
9
 
10
10
  import type { RenderDivergences } from '@barefootjs/jsx'
11
11
 
12
- export const renderDivergences: RenderDivergences = {
13
- }
12
+ // #2679 graduated (capture-before-shadow in `generateDerivedMemoSeed`,
13
+ // packages/adapter-xslate/src/adapter/memo/seed.ts): a self-referencing
14
+ // derived signal/memo now seeds a throwaway `__bf_seed_<name>` local from
15
+ // the RAW-stash-var Kolon lowering BEFORE `$<name>` is declared, then binds
16
+ // the real name off that capture — the same in-template recompute the other
17
+ // six template-stash backends already had. Keep the file even when the set
18
+ // is empty — the next divergence lands here, not in a re-created file.
19
+ export const renderDivergences: RenderDivergences = {}
@@ -568,6 +568,16 @@ function buildPerlProps(
568
568
  // Env signals (#2057) are bound below via `search_params('')`, not from a
569
569
  // static initial value.
570
570
  if (signal.envReader) continue
571
+ // #2669: a self-derivation collision (the signal's own initializer
572
+ // derives from a same-named prop, e.g. `createSignal(props.label ??
573
+ // 'Default')`) already got the correct RAW-prop seed above via
574
+ // `derivedProps` / the user-props loop — `extractSsrDefaults` marks that
575
+ // with a `propName`-carrying entry (see its docstring's invariant).
576
+ // Re-seeding here with this harness's OWN recompute
577
+ // (`evaluateSignalInit`) would clobber the correctly-derived caller
578
+ // value with the harness's evaluated-from-static-props number, papering
579
+ // over the exact production bug this fixture exists to catch.
580
+ if (rootSsrDefaults[signal.getter]?.propName !== undefined) continue
571
581
  const value = evaluateSignalInit(signal.initialValue.trim(), props)
572
582
  if (value !== null) {
573
583
  entries.push(`${signal.getter} => ${toPerlLiteral(value)}`)
@@ -577,6 +587,12 @@ function buildPerlProps(
577
587
  // Memo values seeded from the statically-evaluated ssrDefaults, same
578
588
  // as the production plugin's before_render hook.
579
589
  for (const memo of ir.metadata.memos) {
590
+ // #2669: same self-derivation skip as the signal loop above — a
591
+ // `propName`-carrying memo entry already stands as the correctly
592
+ // prop-derived seed; don't clobber it with the memo's OWN evaluated
593
+ // value (which for a non-idempotent derivation is already the WRONG,
594
+ // double-applied number).
595
+ if (rootSsrDefaults[memo.name]?.propName !== undefined) continue
580
596
  const entry = rootSsrDefaults[memo.name]
581
597
  const value = entry ? entry.value : 0
582
598
  entries.push(`${memo.name} => ${toPerlLiteral(value ?? 0)}`)