@barefootjs/jsx 0.31.7 → 0.31.8

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
@@ -17006,6 +17006,13 @@ function csrInlinableConstantsFromCtx(ctx) {
17006
17006
  }
17007
17007
  return out;
17008
17008
  }
17009
+ function buildTemplateDefPart(ctx, templateHtml) {
17010
+ const envDecls = ctx.signals.filter((s) => Boolean(s.envReader) && Boolean(s.envFactory)).map((s) => `const [${s.getter}] = ${s.envFactory}()`);
17011
+ if (envDecls.length === 0) {
17012
+ return `template: (${PROPS_PARAM}) => \`${templateHtml}\``;
17013
+ }
17014
+ return `template: (${PROPS_PARAM}) => { ${envDecls.join("; ")}; return \`${templateHtml}\` }`;
17015
+ }
17009
17016
  function emitRegistrationAndHydration(lines, ctx, _ir, graph, inlinability) {
17010
17017
  const name = ctx.componentName;
17011
17018
  lines.push(`}`);
@@ -17022,13 +17029,13 @@ function emitRegistrationAndHydration(lines, ctx, _ir, graph, inlinability) {
17022
17029
  if (canGenerateStaticTemplate(_ir.root, propNamesForStaticCheck, inlinableConstants, unsafeLocalNames)) {
17023
17030
  const templateHtml = irToComponentTemplate(_ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName);
17024
17031
  if (templateHtml) {
17025
- defParts.push(`template: (${PROPS_PARAM}) => \`${templateHtml}\``);
17032
+ defParts.push(buildTemplateDefPart(ctx, templateHtml));
17026
17033
  }
17027
17034
  } else {
17028
17035
  const csrInlinableConstants = csrInlinableConstantsFromCtx(ctx);
17029
17036
  const templateHtml = generateCsrTemplate(_ir.root, csrInlinableConstants, ctx, restSpreadNames, ctx.propsObjectName, unsafeLocalNames, ctx.deferredChildSlots);
17030
17037
  if (templateHtml) {
17031
- defParts.push(`template: (${PROPS_PARAM}) => \`${templateHtml}\``);
17038
+ defParts.push(buildTemplateDefPart(ctx, templateHtml));
17032
17039
  }
17033
17040
  }
17034
17041
  if (isCommentScope) {
@@ -1 +1 @@
1
- {"version":3,"file":"csr-substitute.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/csr-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACrC;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAA;AAEtE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;IAC3B,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAA;IACnB,qFAAqF;IACrF,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,MAAM;IACrB;;;;;OAKG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC3C,4EAA4E;IAC5E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,YAAY,GAC5B;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,CAmB7D;AAwMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGtF;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAaxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uCAAuC,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAkBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CA2BR"}
1
+ {"version":3,"file":"csr-substitute.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/csr-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACrC;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC,CAAA;AAEtE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;IAC3B,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAA;IACnB,qFAAqF;IACrF,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,MAAM;IACrB;;;;;OAKG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC3C,4EAA4E;IAC5E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,YAAY,GAC5B;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,CAmB7D;AAwMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGtF;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAaxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uCAAuC,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAUjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sCAAsC,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAkBpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CA4BR"}
@@ -1 +1 @@
1
- {"version":3,"file":"emit-registration.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/emit-registration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAMjD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAmD9G;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,MAAM,GACb;IACD,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC9B,CAGA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAMtF;AAED,4EAA4E;AAC5E;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,eAAe;AACtB;;;;;;GAMG;AACH,YAAY,CAAC,EAAE;IAAE,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GACxF,MAAM,CAqFR"}
1
+ {"version":3,"file":"emit-registration.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/emit-registration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,EAAE,eAAe,EAAc,MAAM,aAAa,CAAA;AAC/F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAMjD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAmD9G;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,MAAM,GACb;IACD,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC9B,CAGA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAMtF;AAkDD,4EAA4E;AAC5E;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,eAAe;AACtB;;;;;;GAMG;AACH,YAAY,CAAC,EAAE;IAAE,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GACxF,MAAM,CAqFR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jsx",
3
- "version": "0.31.7",
3
+ "version": "0.31.8",
4
4
  "description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "directory": "packages/jsx"
50
50
  },
51
51
  "dependencies": {
52
- "@barefootjs/shared": "0.31.7"
52
+ "@barefootjs/shared": "0.31.8"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@barefootjs/client": ">=0.2.0",
@@ -0,0 +1,87 @@
1
+ /**
2
+ * #2654 — the CSR `template:` lambda must declare its own env-signal
3
+ * getter (`createSearchParams()`, #2057) before referencing it, instead
4
+ * of relying on `init`'s `const [<getter>] = createSearchParams()`
5
+ * closure — the template lambda runs at module scope and has no access
6
+ * to that init-local binding, so the bare call ReferenceErrors at
7
+ * template-evaluation time.
8
+ *
9
+ * See `buildTemplateDefPart` in `../ir-to-client-js/emit-registration.ts`.
10
+ */
11
+
12
+ import { describe, test, expect } from 'bun:test'
13
+ import { compileJSX } from '../compiler'
14
+ import { TestAdapter } from '../adapters/test-adapter'
15
+
16
+ const adapter = new TestAdapter()
17
+
18
+ function compileClient(source: string, fileName: string): string {
19
+ const result = compileJSX(source, fileName, { adapter })
20
+ expect(result.errors).toHaveLength(0)
21
+ const clientJs = result.files.find((f) => f.type === 'clientJs')
22
+ return clientJs?.content ?? ''
23
+ }
24
+
25
+ describe('#2654 — env-signal getter self-declared in the template lambda', () => {
26
+ test('default getter name (`searchParams`): template lambda declares its own copy', () => {
27
+ const source = `
28
+ import { createSearchParams } from '@barefootjs/client'
29
+ export function SortLabel() {
30
+ const [searchParams] = createSearchParams()
31
+ return <p>{searchParams().get('sort') ?? 'none'}</p>
32
+ }
33
+ `
34
+ const clientJs = compileClient(source, 'SortLabel.tsx')
35
+
36
+ // The template lambda gets a block body with its own prelude
37
+ // declaration ahead of the returned template literal.
38
+ expect(clientJs).toMatch(
39
+ /template:\s*\(_p\)\s*=>\s*\{\s*const \[searchParams\] = createSearchParams\(\);\s*return\s*`/,
40
+ )
41
+ // The getter call inside the template body is untouched (still a
42
+ // real call — csr-substitute.ts deliberately leaves env-signal
43
+ // getters as live calls, not baked initial values).
44
+ expect(clientJs).toMatch(/\$\{escapeText\(searchParams\(\)\.get\('sort'\) \?\? 'none'\)\}/)
45
+ // `init` keeps its own independent destructure — unaffected by the
46
+ // template-lambda prelude.
47
+ expect(clientJs).toMatch(/export function initSortLabel[\s\S]*const \[searchParams\] = createSearchParams\(\)/)
48
+ })
49
+
50
+ test('aliased getter name (`sp`): prelude uses the destructured alias, not the canonical name', () => {
51
+ const source = `
52
+ 'use client'
53
+ import { createMemo, createSearchParams } from '@barefootjs/client'
54
+ export function SortStatus() {
55
+ const [sp] = createSearchParams()
56
+ const sort = createMemo(() => sp().get('sort') ?? 'date')
57
+ return <p>sort: {sort()}</p>
58
+ }
59
+ `
60
+ const clientJs = compileClient(source, 'SortStatus.tsx')
61
+
62
+ expect(clientJs).toMatch(
63
+ /template:\s*\(_p\)\s*=>\s*\{\s*const \[sp\] = createSearchParams\(\);\s*return\s*`/,
64
+ )
65
+ // The memo body substitution inlines the memo's computation, still
66
+ // calling the self-declared `sp()` getter — no bare, undeclared
67
+ // reference reaches the module-scope lambda.
68
+ expect(clientJs).toMatch(/sp\(\)\.get\('sort'\) \?\? 'date'/)
69
+ })
70
+
71
+ test('component with no env signal keeps the plain expression-body template (no prelude leak)', () => {
72
+ const source = `
73
+ 'use client'
74
+ import { createSignal } from '@barefootjs/client'
75
+ export function Counter() {
76
+ const [count] = createSignal(0)
77
+ return <p>{count()}</p>
78
+ }
79
+ `
80
+ const clientJs = compileClient(source, 'Counter.tsx')
81
+
82
+ // Plain expression-body form — unconditional prelude emission must
83
+ // not leak into components that hold no env signal.
84
+ expect(clientJs).toMatch(/template:\s*\(_p\)\s*=>\s*`/)
85
+ expect(clientJs).not.toMatch(/template:\s*\(_p\)\s*=>\s*\{/)
86
+ })
87
+ })
@@ -452,8 +452,9 @@ export function buildSignalMemoEnv(
452
452
  for (const s of signals) {
453
453
  // Env signals (#2057) have no static initial value to bake — their getter
454
454
  // is a live request-scoped read (`searchParams().get(k)`). Leave it in the
455
- // CSR template as a real call, exactly as the pre-#2057 bare `searchParams()`
456
- // import was (it was never a substitutable signal).
455
+ // CSR template as a real call; `emitRegistrationAndHydration`'s
456
+ // `buildTemplateDefPart` (#2654) gives the template lambda its own
457
+ // `const [<getter>] = <envFactory>()` prelude so the call resolves.
457
458
  if (s.envReader) continue
458
459
  substitutions.set(s.getter, {
459
460
  kind: 'call',
@@ -4,7 +4,7 @@
4
4
  * and the final hydrate() call emission.
5
5
  */
6
6
 
7
- import type { ComponentIR, IRFragment, IRNode, ReferencesGraph } from '../types.ts'
7
+ import type { ComponentIR, IRFragment, IRNode, ReferencesGraph, SignalInfo } from '../types.ts'
8
8
  import type { ClientJsContext } from './types.ts'
9
9
  import { PROPS_PARAM } from './utils.ts'
10
10
  import { computeInlinability, toLegacyInlinability } from './compute-inlinability.ts'
@@ -115,6 +115,54 @@ export function csrInlinableConstantsFromCtx(ctx: ClientJsContext): Map<string,
115
115
  return out
116
116
  }
117
117
 
118
+ /**
119
+ * Build the `template:` ComponentDef entry text for a generated
120
+ * `templateHtml` string.
121
+ *
122
+ * When the component holds one or more env signals (`createSearchParams()`,
123
+ * #2057), the template lambda destructures its own copy of each getter in
124
+ * a block-body prelude before returning the template literal:
125
+ *
126
+ * template: (_p) => { const [sp] = createSearchParams(); return `...` }
127
+ *
128
+ * instead of the plain expression-body form:
129
+ *
130
+ * template: (_p) => `...`
131
+ *
132
+ * The template lambda runs at module scope (`render()` / `renderChild()`),
133
+ * but an env-signal getter is otherwise only ever destructured inside
134
+ * `init...` — so a template that calls the getter directly (`sp()`,
135
+ * `searchParams()`) ReferenceErrors the moment it runs (#2654). Before
136
+ * #2057, `searchParams` was a bare module-scope import and the same
137
+ * template-body call worked by accident; #2057 moved it behind
138
+ * `const [sp] = createSearchParams()` without updating template emission.
139
+ *
140
+ * The prelude is emitted whenever the component HAS an env signal —
141
+ * never gated on whether `templateHtml` textually mentions the getter.
142
+ * Scanning already-emitted template HTML for a getter name would be a
143
+ * string/regex parse of emitted JS, which CLAUDE.md's parse rule forbids.
144
+ * Unconditional emission is safe because `createSearchParams()`
145
+ * (`packages/client/src/reactive.ts`) only returns the shared
146
+ * `searchParamsTuple` module singleton — no side effect — so declaring it
147
+ * in a prelude the template body doesn't end up using costs nothing.
148
+ *
149
+ * `envFactory` is expected to always be set alongside `envReader` (the
150
+ * analyzer sets both together, #2057) — the `undefined` branch is a
151
+ * defensive fallback: if it's ever missing, skip that signal's prelude
152
+ * line entirely rather than guessing a canonical factory name, leaving
153
+ * that one signal's template reference exactly as unsound as before this
154
+ * fix (never worse).
155
+ */
156
+ function buildTemplateDefPart(ctx: ClientJsContext, templateHtml: string): string {
157
+ const envDecls = ctx.signals
158
+ .filter((s): s is SignalInfo & { envFactory: string } => Boolean(s.envReader) && Boolean(s.envFactory))
159
+ .map((s) => `const [${s.getter}] = ${s.envFactory}()`)
160
+ if (envDecls.length === 0) {
161
+ return `template: (${PROPS_PARAM}) => \`${templateHtml}\``
162
+ }
163
+ return `template: (${PROPS_PARAM}) => { ${envDecls.join('; ')}; return \`${templateHtml}\` }`
164
+ }
165
+
118
166
  /** Emit hydrate() call that registers component, template, and hydrates. */
119
167
  /**
120
168
  * Generate the closing brace for the init function and the hydrate() call.
@@ -157,7 +205,7 @@ export function emitRegistrationAndHydration(
157
205
  if (canGenerateStaticTemplate(_ir.root, propNamesForStaticCheck, inlinableConstants, unsafeLocalNames)) {
158
206
  const templateHtml = irToComponentTemplate(_ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName)
159
207
  if (templateHtml) {
160
- defParts.push(`template: (${PROPS_PARAM}) => \`${templateHtml}\``)
208
+ defParts.push(buildTemplateDefPart(ctx, templateHtml))
161
209
  }
162
210
  } else {
163
211
  // CSR fallback: emit for all components that can't generate static templates.
@@ -173,7 +221,7 @@ export function emitRegistrationAndHydration(
173
221
  _ir.root, csrInlinableConstants, ctx, restSpreadNames, ctx.propsObjectName, unsafeLocalNames, ctx.deferredChildSlots
174
222
  )
175
223
  if (templateHtml) {
176
- defParts.push(`template: (${PROPS_PARAM}) => \`${templateHtml}\``)
224
+ defParts.push(buildTemplateDefPart(ctx, templateHtml))
177
225
  }
178
226
  }
179
227
  // No else: top-level-only components skip template entirely (save bytes)