@barefootjs/xslate 0.31.9 → 0.32.0

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(`
@@ -189187,7 +189191,11 @@ var conformancePins = {
189187
189191
  "rich-prop-client-read": [{ code: "BF049", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2648" }]
189188
189192
  };
189189
189193
  // src/render-divergences.ts
189190
- var renderDivergences = {};
189194
+ var renderDivergences = {
189195
+ "todo-app": "the `todos` signal seeds from `(props.initialTodos ?? []).map(t => ({ ...t, editing: false }))` — a different-prop-derived `.map()` chain `extractSsrDefaults` cannot statically resolve, and `computeSsrSeedPlan` classifies it opaque (no in-template recompute), so it seeds `undef`; the non-`/* @client */`-marked `{todos().length > 0 && ...}` toggle-all block SSRs as if there are zero todos regardless of `initialTodos` (https://github.com/piconic-ai/barefootjs/issues/2696)",
189196
+ "todo-app-ssr": "same root cause as `todo-app` (https://github.com/piconic-ai/barefootjs/issues/2696); this fixture's todo-list loop carries no `/* @client */` marker, but Kolon's `for` iterates an undef list as empty rather than raising, so it SSRs an empty `<ul class=\"todo-list\">` with the toggle-all controls also absent, regardless of `initialTodos`",
189197
+ "callback-param-shadows-prop": "the `first` signal seeds from `[{ a: 'p' }].map((title) => title.a).join(',')` — a constant expression `extractSsrDefaults` still cannot statically resolve (`.map()` is unsupported for any receiver) and, unlike the sibling `joined` memo's structurally similar chain, gets no in-template recompute (`computeSsrSeedPlan` classifies it opaque too); `<span>{first()}</span>` SSRs empty instead of `p` (https://github.com/piconic-ai/barefootjs/issues/2696)"
189198
+ };
189191
189199
  export {
189192
189200
  xslateAdapter,
189193
189201
  renderDivergences,
@@ -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,iBAO/B,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.10";
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.32.0",
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.32.0"
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.32.0",
76
+ "@barefootjs/vite": "0.32.0",
77
+ "@barefootjs/client": "0.32.0",
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,18 @@
9
9
 
10
10
  import type { RenderDivergences } from '@barefootjs/jsx'
11
11
 
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.
12
19
  export const renderDivergences: RenderDivergences = {
20
+ 'todo-app':
21
+ 'the `todos` signal seeds from `(props.initialTodos ?? []).map(t => ({ ...t, editing: false }))` — a different-prop-derived `.map()` chain `extractSsrDefaults` cannot statically resolve, and `computeSsrSeedPlan` classifies it opaque (no in-template recompute), so it seeds `undef`; the non-`/* @client */`-marked `{todos().length > 0 && ...}` toggle-all block SSRs as if there are zero todos regardless of `initialTodos` (https://github.com/piconic-ai/barefootjs/issues/2696)',
22
+ 'todo-app-ssr':
23
+ "same root cause as `todo-app` (https://github.com/piconic-ai/barefootjs/issues/2696); this fixture's todo-list loop carries no `/* @client */` marker, but Kolon's `for` iterates an undef list as empty rather than raising, so it SSRs an empty `<ul class=\"todo-list\">` with the toggle-all controls also absent, regardless of `initialTodos`",
24
+ 'callback-param-shadows-prop':
25
+ "the `first` signal seeds from `[{ a: 'p' }].map((title) => title.a).join(',')` — a constant expression `extractSsrDefaults` still cannot statically resolve (`.map()` is unsupported for any receiver) and, unlike the sibling `joined` memo's structurally similar chain, gets no in-template recompute (`computeSsrSeedPlan` classifies it opaque too); `<span>{first()}</span>` SSRs empty instead of `p` (https://github.com/piconic-ai/barefootjs/issues/2696)",
13
26
  }
@@ -17,7 +17,7 @@
17
17
  * than the Mojo harness's literal `test_<sN>`.
18
18
  */
19
19
 
20
- import { compileJSX, extractSsrDefaults, deriveStashFromDefaults, importsSearchParams, evaluateSignalInit } from '@barefootjs/jsx'
20
+ import { compileJSX, extractSsrDefaults, deriveStashFromDefaults, importsSearchParams } from '@barefootjs/jsx'
21
21
  import type { ComponentIR, SsrDefault } from '@barefootjs/jsx'
22
22
  import { mkdir, rm } from 'node:fs/promises'
23
23
  import { resolve } from 'node:path'
@@ -563,23 +563,22 @@ function buildPerlProps(
563
563
  }
564
564
  }
565
565
 
566
- // Signal values evaluated from props (after user props).
566
+ // No initializer re-evaluation against raw props production's
567
+ // before_render seeds only from `deriveStashFromDefaults` (#2696).
567
568
  for (const signal of ir.metadata.signals) {
568
569
  // Env signals (#2057) are bound below via `search_params('')`, not from a
569
570
  // static initial value.
570
571
  if (signal.envReader) continue
571
- const value = evaluateSignalInit(signal.initialValue.trim(), props)
572
- if (value !== null) {
573
- entries.push(`${signal.getter} => ${toPerlLiteral(value)}`)
572
+ if (Object.prototype.hasOwnProperty.call(derivedProps, signal.getter)) {
573
+ entries.push(`${signal.getter} => ${toPerlLiteral(derivedProps[signal.getter])}`)
574
574
  }
575
575
  }
576
576
 
577
- // Memo values seeded from the statically-evaluated ssrDefaults, same
578
- // as the production plugin's before_render hook.
577
+ // No `?? 0` for entries the manifest lacks — production seeds nothing there (#2696).
579
578
  for (const memo of ir.metadata.memos) {
580
- const entry = rootSsrDefaults[memo.name]
581
- const value = entry ? entry.value : 0
582
- entries.push(`${memo.name} => ${toPerlLiteral(value ?? 0)}`)
579
+ if (Object.prototype.hasOwnProperty.call(derivedProps, memo.name)) {
580
+ entries.push(`${memo.name} => ${toPerlLiteral(derivedProps[memo.name])}`)
581
+ }
583
582
  }
584
583
 
585
584
  // (#1922) Request-scoped `searchParams()`: bind `$searchParams` to an