@barefootjs/jsx 0.17.0 → 0.18.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.
- package/dist/adapters/env-signal.d.ts +42 -7
- package/dist/adapters/env-signal.d.ts.map +1 -1
- package/dist/adapters/interface.d.ts +20 -4
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/adapters/parsed-expr-emitter.d.ts +3 -1
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/augment-inherited-props.d.ts +19 -0
- package/dist/augment-inherited-props.d.ts.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +48 -2
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1728 -969
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/compute-inlinability.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +9 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts +19 -0
- package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-registration.d.ts +4 -2
- package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +41 -0
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +22 -1
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/loop-destructure.d.ts +55 -18
- package/dist/loop-destructure.d.ts.map +1 -1
- package/dist/lowering-registry.d.ts +13 -0
- package/dist/lowering-registry.d.ts.map +1 -1
- package/dist/relocate.d.ts +28 -0
- package/dist/relocate.d.ts.map +1 -1
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/ssr-seed-plan.d.ts +84 -0
- package/dist/ssr-seed-plan.d.ts.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +284 -12
- package/src/__tests__/augment-inherited-props.test.ts +96 -0
- package/src/__tests__/compiler-runtime-contract.test.ts +11 -1
- package/src/__tests__/compiler-stress-1244.test.ts +13 -4
- package/src/__tests__/csr-substitution-safety-divergence.test.ts +137 -0
- package/src/__tests__/destructured-map-params.test.ts +11 -1
- package/src/__tests__/expression-parser.test.ts +74 -3
- package/src/__tests__/free-identifiers.test.ts +55 -0
- package/src/__tests__/ir-sort-comparator.test.ts +261 -0
- package/src/__tests__/loop-destructure.test.ts +313 -0
- package/src/__tests__/loop-hoisted-template.test.ts +235 -0
- package/src/__tests__/materialize-getter-calls.test.ts +58 -0
- package/src/__tests__/props-destructuring.test.ts +110 -0
- package/src/__tests__/serialize-parsed-expr.test.ts +88 -2
- package/src/__tests__/ssr-defaults.test.ts +20 -0
- package/src/__tests__/ssr-seed-plan.test.ts +212 -0
- package/src/__tests__/staged-ir/11-template-primitive-registry.test.ts +231 -1
- package/src/__tests__/tagged-template-interleave.test.ts +268 -0
- package/src/__tests__/unsupported-expression.test.ts +194 -7
- package/src/adapters/env-signal.ts +57 -9
- package/src/adapters/interface.ts +20 -4
- package/src/adapters/parsed-expr-emitter.ts +19 -2
- package/src/analyzer-context.ts +20 -0
- package/src/analyzer.ts +74 -1
- package/src/augment-inherited-props.ts +139 -9
- package/src/compiler.ts +10 -1
- package/src/expression-parser.ts +421 -50
- package/src/index.ts +30 -3
- package/src/ir-to-client-js/collect-elements.ts +15 -1
- package/src/ir-to-client-js/compute-inlinability.ts +6 -1
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +1 -0
- package/src/ir-to-client-js/control-flow/plan/loop.ts +9 -0
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +30 -8
- package/src/ir-to-client-js/control-flow/stringify/template-parse.ts +30 -0
- package/src/ir-to-client-js/emit-registration.ts +4 -2
- package/src/ir-to-client-js/html-template.ts +198 -1
- package/src/ir-to-client-js/index.ts +1 -0
- package/src/ir-to-client-js/types.ts +22 -0
- package/src/jsx-to-ir.ts +409 -24
- package/src/loop-destructure.ts +89 -36
- package/src/lowering-registry.ts +16 -0
- package/src/relocate.ts +201 -14
- package/src/ssr-defaults.ts +34 -32
- package/src/ssr-seed-plan.ts +146 -0
- package/src/types.ts +76 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-neutral SSR seed plan — which signals/memos an adapter may seed
|
|
3
|
+
* in-template at SSR time, and from what scope.
|
|
4
|
+
*
|
|
5
|
+
* Design principle: the IR/analyzer side ANALYZES and attaches structured
|
|
6
|
+
* information; adapters only EMIT. The "is this binding derivable from names
|
|
7
|
+
* already in template scope" decision used to live (triplicated) in the
|
|
8
|
+
* template adapters' seed paths; this module computes it once, on the IR, so
|
|
9
|
+
* every adapter consumes the same plan and only supplies its own syntax.
|
|
10
|
+
*
|
|
11
|
+
* Ordering / acyclicity guarantee: `steps` lists the component's signals
|
|
12
|
+
* first, then its memos, each group in declaration order (matching
|
|
13
|
+
* `IRMetadata.signals` / `IRMetadata.memos`). A binding's name only enters
|
|
14
|
+
* the scope set AFTER its own step is decided, so a `derived` step's `frees`
|
|
15
|
+
* can only name `baseScope` entries or EARLIER steps — self- and
|
|
16
|
+
* forward-references are rejected by construction, and a consumer emitting
|
|
17
|
+
* the steps top-to-bottom never reads an undeclared local.
|
|
18
|
+
*
|
|
19
|
+
* Module-scope pure-string consts count as in-scope (they are part of
|
|
20
|
+
* `baseScope`) because every adapter compile-time-inlines them to their
|
|
21
|
+
* literal value — `collectModuleStringConsts` is the shared source of that
|
|
22
|
+
* set — so a reference to one is never a template-variable read.
|
|
23
|
+
*
|
|
24
|
+
* A `derived` step with EMPTY `frees` is a constant expression (e.g.
|
|
25
|
+
* `createSignal('b')`): the plan still classifies it as derived because the
|
|
26
|
+
* expression is fully analyzable; emit-side constant-skipping (adapters keep
|
|
27
|
+
* their existing ssr-defaults seeding for such inits) is an adapter concern,
|
|
28
|
+
* not a plan concern. Likewise the plan makes no backend-specific choices —
|
|
29
|
+
* no target-variable checks, no self-shadowing rules, no per-backend shape
|
|
30
|
+
* catalogs — those stay in the adapters.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { collectModuleStringConsts } from './augment-inherited-props.ts'
|
|
34
|
+
import { envSignalReaderFor, type EnvSignalReader } from './adapters/env-signal.ts'
|
|
35
|
+
import {
|
|
36
|
+
extractArrowBodyExpression,
|
|
37
|
+
freeIdentifiers,
|
|
38
|
+
isSupported,
|
|
39
|
+
parseExpression,
|
|
40
|
+
type ParsedExpr,
|
|
41
|
+
} from './expression-parser.ts'
|
|
42
|
+
import type { IRMetadata } from './types.ts'
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* One binding in component declaration order (signals first, then memos —
|
|
46
|
+
* matching `IRMetadata` order and the adapters' iteration).
|
|
47
|
+
*
|
|
48
|
+
* - `env-reader`: an env signal whose `envReader` key resolves in the shared
|
|
49
|
+
* registry (`envSignalReaderFor`). The runtime provides the per-request
|
|
50
|
+
* reader, so there is nothing to seed; the name still enters scope so a
|
|
51
|
+
* later derived step may reference it. An `envReader` key UNKNOWN to the
|
|
52
|
+
* registry falls through to the normal derived/opaque rules instead.
|
|
53
|
+
* - `derived`: the binding's value expression is a supported shape whose free
|
|
54
|
+
* identifiers are all in scope at this point (baseScope + earlier steps) —
|
|
55
|
+
* an adapter may seed it in-template by lowering `parsed`/`expr`.
|
|
56
|
+
* - `opaque`: not seedable this way (empty init, unsupported shape,
|
|
57
|
+
* unanalyzable free set, out-of-scope reference, or a block-bodied memo).
|
|
58
|
+
* The name still enters scope for later steps; adapters keep their static
|
|
59
|
+
* ssr-defaults seeding for it.
|
|
60
|
+
*/
|
|
61
|
+
export type SsrSeedStep =
|
|
62
|
+
| { kind: 'env-reader'; name: string; reader: EnvSignalReader }
|
|
63
|
+
| { kind: 'derived'; name: string; origin: 'signal' | 'memo'; expr: string; parsed: ParsedExpr; frees: string[] }
|
|
64
|
+
| { kind: 'opaque'; name: string; origin: 'signal' | 'memo' }
|
|
65
|
+
|
|
66
|
+
export interface SsrSeedPlan {
|
|
67
|
+
/**
|
|
68
|
+
* Names in scope before any step: props params, the props-object name
|
|
69
|
+
* (when the component takes an undestructured props object), and module
|
|
70
|
+
* pure-string consts (compile-time inlined by every adapter).
|
|
71
|
+
*/
|
|
72
|
+
baseScope: string[]
|
|
73
|
+
steps: SsrSeedStep[]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Classify one value expression against the current scope: `derived` when it
|
|
78
|
+
* parses to a supported shape whose free identifiers are all `available`
|
|
79
|
+
* (an unanalyzable free set — `freeIdentifiers` → null — fails safe to
|
|
80
|
+
* opaque). The scope check runs over the parsed SOURCE tree, so a shadowed
|
|
81
|
+
* name (`items.filter((p) => p.ok) && p`, where the trailing `p` is a
|
|
82
|
+
* different, unbound reference from the callback's own param) is rejected.
|
|
83
|
+
*/
|
|
84
|
+
function classify(
|
|
85
|
+
name: string,
|
|
86
|
+
origin: 'signal' | 'memo',
|
|
87
|
+
expr: string,
|
|
88
|
+
parsed: ParsedExpr,
|
|
89
|
+
available: ReadonlySet<string>,
|
|
90
|
+
): SsrSeedStep {
|
|
91
|
+
if (!isSupported(parsed).supported) return { kind: 'opaque', name, origin }
|
|
92
|
+
const frees = freeIdentifiers(parsed)
|
|
93
|
+
if (frees === null) return { kind: 'opaque', name, origin }
|
|
94
|
+
for (const free of frees) {
|
|
95
|
+
if (!available.has(free)) return { kind: 'opaque', name, origin }
|
|
96
|
+
}
|
|
97
|
+
return { kind: 'derived', name, origin, expr, parsed, frees: [...frees] }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Compute the component's SSR seed plan from its metadata. See the module
|
|
102
|
+
* doc for the contract. Memo steps are gated to EXPRESSION-BODIED memos
|
|
103
|
+
* (`extractArrowBodyExpression` returns the body): a block-bodied memo is
|
|
104
|
+
* `opaque` even when the analyzer folded it to a `parsed` expression.
|
|
105
|
+
*/
|
|
106
|
+
export function computeSsrSeedPlan(metadata: IRMetadata): SsrSeedPlan {
|
|
107
|
+
const baseScope: string[] = metadata.propsParams.map(p => p.name)
|
|
108
|
+
if (metadata.propsObjectName) baseScope.push(metadata.propsObjectName)
|
|
109
|
+
for (const name of collectModuleStringConsts(metadata.localConstants).keys()) {
|
|
110
|
+
baseScope.push(name)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const available = new Set<string>(baseScope)
|
|
114
|
+
const steps: SsrSeedStep[] = []
|
|
115
|
+
|
|
116
|
+
for (const signal of metadata.signals) {
|
|
117
|
+
if (signal.envReader) {
|
|
118
|
+
const reader = envSignalReaderFor(signal.envReader)
|
|
119
|
+
if (reader) {
|
|
120
|
+
steps.push({ kind: 'env-reader', name: signal.getter, reader })
|
|
121
|
+
available.add(signal.getter)
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const expr = signal.initialValue.trim()
|
|
126
|
+
steps.push(
|
|
127
|
+
expr === ''
|
|
128
|
+
? { kind: 'opaque', name: signal.getter, origin: 'signal' }
|
|
129
|
+
: classify(signal.getter, 'signal', expr, parseExpression(expr), available),
|
|
130
|
+
)
|
|
131
|
+
available.add(signal.getter)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const memo of metadata.memos) {
|
|
135
|
+
const body = extractArrowBodyExpression(memo.computation)
|
|
136
|
+
const expr = body?.trim() ?? ''
|
|
137
|
+
steps.push(
|
|
138
|
+
expr === ''
|
|
139
|
+
? { kind: 'opaque', name: memo.name, origin: 'memo' }
|
|
140
|
+
: classify(memo.name, 'memo', expr, memo.parsed ?? parseExpression(expr), available),
|
|
141
|
+
)
|
|
142
|
+
available.add(memo.name)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { baseScope, steps }
|
|
146
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ParsedExpr, ParsedStatement } from './expression-parser.ts'
|
|
8
|
+
import type { SsrSeedPlan } from './ssr-seed-plan.ts'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Loop-hoisted sort comparator for the `.sort().map()` / `.toSorted().map()`
|
|
@@ -694,6 +695,26 @@ export interface RestExcludeKey {
|
|
|
694
695
|
isIdent: boolean
|
|
695
696
|
}
|
|
696
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Structured, non-string form of a `.map()` callback destructure accessor
|
|
700
|
+
* step — one entry per `.foo` / `["a-b"]` / `[0]` step folded into
|
|
701
|
+
* {@link LoopParamBinding.path}. Adapters that need to build their own
|
|
702
|
+
* accessor expression (rather than splice `path` in as a JS suffix) walk
|
|
703
|
+
* `segments` instead of parsing `path` with a regex (repo rule: never parse
|
|
704
|
+
* JS/TS syntax with regex or string matching).
|
|
705
|
+
*
|
|
706
|
+
* - `{ kind: 'field', key, isIdent }`: an object-property step. `isIdent`
|
|
707
|
+
* uses the same `ts.isIdentifierStart`/`isIdentifierPart` classification
|
|
708
|
+
* as {@link RestExcludeKey.isIdent} — an adapter choosing between a native
|
|
709
|
+
* `.foo` accessor and a quoted/bracketed one (`["data-priority"]`, a
|
|
710
|
+
* quoted map key, ...) reads this instead of re-deriving the identifier
|
|
711
|
+
* rule itself.
|
|
712
|
+
* - `{ kind: 'index', index }`: an array-position step (`[0]`, `[1]`, ...).
|
|
713
|
+
*/
|
|
714
|
+
export type LoopBindingPathSegment =
|
|
715
|
+
| { kind: 'field'; key: string; isIdent: boolean }
|
|
716
|
+
| { kind: 'index'; index: number }
|
|
717
|
+
|
|
697
718
|
/**
|
|
698
719
|
* Destructured binding extracted from a `.map()` callback's item parameter.
|
|
699
720
|
*
|
|
@@ -720,6 +741,24 @@ export interface LoopParamBinding {
|
|
|
720
741
|
rest?:
|
|
721
742
|
| { kind: 'object'; exclude: readonly RestExcludeKey[] }
|
|
722
743
|
| { kind: 'array'; from: number }
|
|
744
|
+
/**
|
|
745
|
+
* Structured form of `path` (see {@link LoopBindingPathSegment}).
|
|
746
|
+
*
|
|
747
|
+
* - Fixed bindings (`rest` unset): the full accessor from the loop item to
|
|
748
|
+
* this binding, one segment per `path` step. Always non-empty — a fixed
|
|
749
|
+
* binding always has at least one accessor step.
|
|
750
|
+
* - Rest bindings (`rest` set): the structured form of the PARENT prefix
|
|
751
|
+
* (i.e. of `path`, which for rest bindings holds the parent prefix, not
|
|
752
|
+
* an accessor to the rest binding itself) — possibly empty when the
|
|
753
|
+
* rest sits at the loop root (`({ ...rest }) => …` / `([...rest]) => …`).
|
|
754
|
+
*
|
|
755
|
+
* Optional only so older/foreign IR (built before this field existed)
|
|
756
|
+
* still type-checks; `extractLoopParamBindings` always populates it now.
|
|
757
|
+
* Downstream gates (`isLowerableLoopDestructure`) treat a missing
|
|
758
|
+
* `segments` as "unknown shape" and refuse conservatively rather than
|
|
759
|
+
* reading it as "binding at the loop root".
|
|
760
|
+
*/
|
|
761
|
+
segments?: readonly LoopBindingPathSegment[]
|
|
723
762
|
}
|
|
724
763
|
|
|
725
764
|
export interface IRComponent {
|
|
@@ -1526,6 +1565,16 @@ export interface IRMetadata {
|
|
|
1526
1565
|
* resolves the compiled module rather than the source `.tsx`.
|
|
1527
1566
|
*/
|
|
1528
1567
|
clientSignalImportSources?: Set<string>
|
|
1568
|
+
/**
|
|
1569
|
+
* Backend-neutral SSR seed plan: per-binding derived/opaque/env-reader
|
|
1570
|
+
* classification in declaration order, plus the base scope, computed by
|
|
1571
|
+
* `computeSsrSeedPlan` from this metadata. Attached by `buildMetadata` and
|
|
1572
|
+
* serialized into IR JSON like the rest of the metadata; template adapters
|
|
1573
|
+
* consume it instead of re-deriving scope/derivability themselves (their
|
|
1574
|
+
* target-syntax choices — lowering, self-shadow rules, constant-emit
|
|
1575
|
+
* guards — stay adapter-side).
|
|
1576
|
+
*/
|
|
1577
|
+
ssrSeedPlan?: SsrSeedPlan
|
|
1529
1578
|
}
|
|
1530
1579
|
|
|
1531
1580
|
// =============================================================================
|
|
@@ -1699,6 +1748,24 @@ export interface ErrorSuggestion {
|
|
|
1699
1748
|
replacement?: string
|
|
1700
1749
|
}
|
|
1701
1750
|
|
|
1751
|
+
/**
|
|
1752
|
+
* A diagnostic an adapter intentionally emits for a shared conformance
|
|
1753
|
+
* fixture (packages/adapter-tests) instead of lowering it — the adapter's
|
|
1754
|
+
* machine-readable known-limitations declaration. Consumed by the adapter's
|
|
1755
|
+
* own conformance test (as `expectedDiagnostics`) and by `bf compat`
|
|
1756
|
+
* (issue-URL attribution). Tracked limitations carry the `known-limitation`
|
|
1757
|
+
* label: https://github.com/piconic-ai/barefootjs/labels/known-limitation
|
|
1758
|
+
*/
|
|
1759
|
+
export interface ConformancePin {
|
|
1760
|
+
/** Diagnostic code, e.g. 'BF101'. */
|
|
1761
|
+
code: string
|
|
1762
|
+
severity: 'error' | 'warning'
|
|
1763
|
+
/** Tracking issue URL (known-limitation label) for this refusal, when one exists. */
|
|
1764
|
+
issue?: string
|
|
1765
|
+
}
|
|
1766
|
+
/** Keyed by shared-fixture id (`JSXFixture.id`). */
|
|
1767
|
+
export type ConformancePins = Record<string, ReadonlyArray<ConformancePin>>
|
|
1768
|
+
|
|
1702
1769
|
// =============================================================================
|
|
1703
1770
|
// Compile Options & Results
|
|
1704
1771
|
// =============================================================================
|
|
@@ -1759,6 +1826,15 @@ export interface FileOutput {
|
|
|
1759
1826
|
path: string
|
|
1760
1827
|
content: string
|
|
1761
1828
|
type: 'markedTemplate' | 'clientJs' | 'ir' | 'sourceMap' | 'types' | 'ssrDefaults'
|
|
1829
|
+
/**
|
|
1830
|
+
* The exported component this file was generated for. Set on
|
|
1831
|
+
* `markedTemplate` / `ssrDefaults` outputs so the build pipeline can pair
|
|
1832
|
+
* them per component without guessing from file basenames — a
|
|
1833
|
+
* single-component file's template is named after the SOURCE file
|
|
1834
|
+
* (`index.html.ep`), not the component, so the basename alone can't
|
|
1835
|
+
* recover the component name (#2132).
|
|
1836
|
+
*/
|
|
1837
|
+
componentName?: string
|
|
1762
1838
|
}
|
|
1763
1839
|
|
|
1764
1840
|
export interface CompileResult {
|