@barefootjs/rust 0.1.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/README.md +194 -0
- package/dist/adapter/analysis/component-tree.d.ts +26 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/boolean-result.d.ts +85 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +107 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +75 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +143 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +189091 -0
- package/dist/adapter/lib/constants.d.ts +25 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +50 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/minijinja-naming.d.ts +64 -0
- package/dist/adapter/lib/minijinja-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +32 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +84 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/minijinja-adapter.d.ts +421 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +33 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +63 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +28 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189111 -0
- package/dist/conformance-pins.d.ts +13 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189112 -0
- package/package.json +67 -0
- package/runtime/Cargo.lock +124 -0
- package/runtime/Cargo.toml +21 -0
- package/runtime/src/backend_minijinja.rs +176 -0
- package/runtime/src/bin/bf-render.rs +147 -0
- package/runtime/src/evaluator.rs +770 -0
- package/runtime/src/lib.rs +19 -0
- package/runtime/src/manifest.rs +258 -0
- package/runtime/src/num.rs +558 -0
- package/runtime/src/runtime.rs +1548 -0
- package/runtime/src/search_params.rs +173 -0
- package/runtime/tests/eval_vectors.rs +94 -0
- package/runtime/tests/evaluator.rs +407 -0
- package/runtime/tests/helper_vectors.rs +348 -0
- package/runtime/tests/manifest.rs +169 -0
- package/runtime/tests/omit.rs +79 -0
- package/runtime/tests/props_attr.rs +75 -0
- package/runtime/tests/query.rs +50 -0
- package/runtime/tests/render_child.rs +210 -0
- package/runtime/tests/search_params.rs +68 -0
- package/runtime/tests/spread_attrs.rs +94 -0
- package/runtime/tests/template_primitives.rs +376 -0
- package/runtime/tests/vector-divergences.json +33 -0
- package/src/__tests__/minijinja-adapter-unit.test.ts +392 -0
- package/src/__tests__/minijinja-adapter.test.ts +58 -0
- package/src/__tests__/minijinja-counter.test.ts +61 -0
- package/src/__tests__/minijinja-query-href.test.ts +101 -0
- package/src/__tests__/minijinja-spread-attrs.test.ts +227 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/boolean-result.ts +177 -0
- package/src/adapter/emit-context.ts +119 -0
- package/src/adapter/expr/array-method.ts +346 -0
- package/src/adapter/expr/emitters.ts +608 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/lib/constants.ts +37 -0
- package/src/adapter/lib/ir-scope.ts +95 -0
- package/src/adapter/lib/minijinja-naming.ts +85 -0
- package/src/adapter/lib/types.ts +35 -0
- package/src/adapter/memo/seed.ts +135 -0
- package/src/adapter/minijinja-adapter.ts +1796 -0
- package/src/adapter/props/prop-classes.ts +65 -0
- package/src/adapter/spread/spread-codegen.ts +168 -0
- package/src/adapter/value/parsed-literal.ts +76 -0
- package/src/build.ts +38 -0
- package/src/conformance-pins.ts +101 -0
- package/src/index.ts +12 -0
- package/src/test-render.ts +680 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR traversal helpers for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/lib/ir-scope.ts`.
|
|
5
|
+
* `resolveJsxChildrenProp` and `collectRootScopeNodes` are byte-identical
|
|
6
|
+
* (adapter-agnostic IR walks).
|
|
7
|
+
*
|
|
8
|
+
* `extractTopLevelIdentifiers` scans the RENDERED template text (rather than
|
|
9
|
+
* re-deriving free vars from the original JS AST) so it stays exactly in
|
|
10
|
+
* sync with whatever the emitter actually produced — but Jinja identifiers
|
|
11
|
+
* have no sigil (unlike Kolon's `$`, which made a trivially safe
|
|
12
|
+
* `\$([A-Za-z_]\w*)` scan possible): a bare word in the rendered text could
|
|
13
|
+
* be a genuine context-var reference, a `bf.` runtime-helper method name, a
|
|
14
|
+
* Jinja grammar keyword emitted by this adapter's own condition/ternary
|
|
15
|
+
* lowering (`if`/`else`/`is`/`not`/`and`/`or`/`none`/`true`/`false`), or
|
|
16
|
+
* content inside a single-quoted string literal. This helper makes the scan
|
|
17
|
+
* sound: it strips quoted string spans first, then matches identifier tokens
|
|
18
|
+
* NOT immediately preceded by a `.` (excluding dotted property/method names
|
|
19
|
+
* — the same exclusion the `$` sigil gave Kolon for free, since Kolon's
|
|
20
|
+
* regex only ever matched right after `$`), then drops the closed set of
|
|
21
|
+
* tokens this adapter's own codegen can emit that aren't context vars (`bf`
|
|
22
|
+
* and the Jinja keywords above). `memo/seed.ts` uses it to detect a
|
|
23
|
+
* constant lowering (no top-level identifier at all) that should keep the
|
|
24
|
+
* static ssr-defaults seed instead of an in-template `{% set %}`; scope
|
|
25
|
+
* AVAILABILITY itself is now the shared `computeSsrSeedPlan`'s job
|
|
26
|
+
* (packages/jsx/src/ssr-seed-plan.ts), not this module's (#2075).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { IRNode, IRProp, IRIfStatement, IRFragment } from '@barefootjs/jsx'
|
|
30
|
+
|
|
31
|
+
/** Tokens this adapter's own codegen can emit that are never context vars. */
|
|
32
|
+
const NON_VAR_TOKENS = new Set([
|
|
33
|
+
'bf', 'if', 'else', 'is', 'not', 'and', 'or', 'in', 'none', 'true', 'false',
|
|
34
|
+
// `is defined` — the nullish-coalescing (`??`) lowering's Undefined guard
|
|
35
|
+
// (`expr/emitters.ts`'s `logical`, and the nullable-optional-prop
|
|
36
|
+
// attribute-omission test in `minijinja-adapter.ts`) emits this Jinja test
|
|
37
|
+
// keyword; it's never a context var.
|
|
38
|
+
'defined',
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract the set of "top-level identifier" tokens from a rendered Jinja
|
|
43
|
+
* expression: bare words, excluding quoted-string content, dotted
|
|
44
|
+
* property/method names, and this adapter's own non-var keyword vocabulary.
|
|
45
|
+
* See the file header for why this replaces a direct `\w+` scan.
|
|
46
|
+
*/
|
|
47
|
+
export function extractTopLevelIdentifiers(jinjaExpr: string): string[] {
|
|
48
|
+
// Strip single-quoted string literals (this adapter only ever emits
|
|
49
|
+
// single-quoted string literals, backslash-escaped) so their content can't
|
|
50
|
+
// leak into the identifier scan.
|
|
51
|
+
const stripped = jinjaExpr.replace(/'(?:\\.|[^'\\])*'/g, ' ')
|
|
52
|
+
const out: string[] = []
|
|
53
|
+
for (const m of stripped.matchAll(/(?<!\.)\b([A-Za-z_]\w*)\b/g)) {
|
|
54
|
+
if (!NON_VAR_TOKENS.has(m[1])) out.push(m[1])
|
|
55
|
+
}
|
|
56
|
+
return out
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find the `children` prop's `jsx-children` payload. Narrowed via the
|
|
61
|
+
* AttrValue `kind` discriminator so adapter code stays type-safe if the IR
|
|
62
|
+
* shape evolves.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveJsxChildrenProp(props: readonly IRProp[]): IRNode[] {
|
|
65
|
+
const prop = props.find(p => p.name === 'children')
|
|
66
|
+
if (!prop) return []
|
|
67
|
+
if (prop.value.kind !== 'jsx-children') return []
|
|
68
|
+
return prop.value.children
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Collect the component's root scope element node(s) — the elements that
|
|
73
|
+
* become the rendered root and so carry `data-key` for a keyed loop item. A
|
|
74
|
+
* plain element root is itself; an `if-statement` (early-return) root
|
|
75
|
+
* contributes the top element of each branch, since exactly one renders at
|
|
76
|
+
* runtime. (#1297)
|
|
77
|
+
*/
|
|
78
|
+
export function collectRootScopeNodes(node: IRNode): Set<IRNode> {
|
|
79
|
+
const out = new Set<IRNode>()
|
|
80
|
+
const visit = (n: IRNode | null): void => {
|
|
81
|
+
if (!n) return
|
|
82
|
+
if (n.type === 'element') { out.add(n); return }
|
|
83
|
+
if (n.type === 'if-statement') {
|
|
84
|
+
const s = n as IRIfStatement
|
|
85
|
+
visit(s.consequent)
|
|
86
|
+
visit(s.alternate)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
if (n.type === 'fragment') {
|
|
90
|
+
for (const c of (n as IRFragment).children) visit(c)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
visit(node)
|
|
94
|
+
return out
|
|
95
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jinja2 identifier and dict-key conventions for the minijinja adapter.
|
|
3
|
+
*
|
|
4
|
+
* Near-verbatim port of the adapter-jinja equivalent
|
|
5
|
+
* (`packages/adapter-jinja/src/adapter/lib/jinja-naming.ts`, itself ported
|
|
6
|
+
* from the Kolon equivalent,
|
|
7
|
+
* `packages/adapter-xslate/src/adapter/lib/kolon-naming.ts`), adjusted for
|
|
8
|
+
* two real syntax divergences between Kolon and Jinja2. The RESERVED_WORDS
|
|
9
|
+
* set below is the IDENTICAL Python-keyword list adapter-jinja uses — kept
|
|
10
|
+
* unchanged rather than re-derived for minijinja, since the point is for the
|
|
11
|
+
* TS emitter and the Rust runtime's `mangle_ident` to agree on one set, not
|
|
12
|
+
* to find the minimal set minijinja's own grammar requires:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Dict-literal keys are always quoted.** Kolon's hashref fat-comma
|
|
15
|
+
* (`key => value`) auto-quotes a bareword key, so `kolonHashKey` only
|
|
16
|
+
* quotes a non-identifier-safe name (`'data-slot'`). A Jinja/Python dict
|
|
17
|
+
* LITERAL has no such bareword-key sugar — `{key: value}` means "look up
|
|
18
|
+
* the *variable* `key` and use its value as the key", not the string
|
|
19
|
+
* `"key"`. `minijinjaHashKey` therefore ALWAYS emits a quoted string literal,
|
|
20
|
+
* identifier-safe or not — the one place this port is NOT a bare 1:1
|
|
21
|
+
* syntax substitution, because treating it as one would silently change
|
|
22
|
+
* dict keys into undefined-variable lookups.
|
|
23
|
+
* 2. **Reserved-word identifier mangling.** adapter-jinja verified empirically
|
|
24
|
+
* against Python's Jinja 3.1 that most Python keywords (`class`, `import`,
|
|
25
|
+
* `for`, …) parse fine as bare Jinja variable / loop-target names — Jinja
|
|
26
|
+
* compiles every user template name to an internally-prefixed Python local
|
|
27
|
+
* (`l_0_…`), so the template layer itself mostly sidesteps Python's own
|
|
28
|
+
* reserved-word rules. minijinja 2.21 was re-verified compatible by the
|
|
29
|
+
* orchestrator spike (same dict-literal / `{% set %}` / elif / `~` concat
|
|
30
|
+
* / `| safe` behavior). The genuinely Jinja-reserved bare words are `not`
|
|
31
|
+
* / `and` / `or` / `in` / `is` / `if` / `else` (grammar keywords) plus the
|
|
32
|
+
* three constants `true` / `false` / `none` (rejected specifically as
|
|
33
|
+
* assignment targets). Per the adapter plan, `minijinjaIdent` mangles the
|
|
34
|
+
* FULL Python reserved-word list (a conservative superset of what Jinja's
|
|
35
|
+
* grammar strictly requires) with a trailing-underscore suffix, trivial
|
|
36
|
+
* and easy to keep in lock-step with the Rust runtime's identical rule
|
|
37
|
+
* (`mangle_ident` in `render_named`, `packages/adapter-rust/runtime/src/backend_minijinja.rs`)
|
|
38
|
+
* — the runtime applies the SAME mangling when it builds the per-render
|
|
39
|
+
* context, so a prop/signal named e.g. `class` is threaded through as
|
|
40
|
+
* context key `'class_'` on both sides.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Escape a string for a Jinja/Python single-quoted literal: backslash first
|
|
45
|
+
* (so it doesn't double-escape the quote we add next), then the quote.
|
|
46
|
+
* Python string-literal escaping treats `\\` / `\'` identically to Perl's,
|
|
47
|
+
* so this is byte-identical to `escapeKolonSingleQuoted`.
|
|
48
|
+
*/
|
|
49
|
+
export function escapeMinijinjaSingleQuoted(s: string): string {
|
|
50
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Quote a dict-literal KEY for Jinja. UNLIKE Kolon's `kolonHashKey`, this
|
|
55
|
+
* always quotes — see the file header for why a bareword key would silently
|
|
56
|
+
* become a variable lookup instead of a string key.
|
|
57
|
+
*/
|
|
58
|
+
export function minijinjaHashKey(name: string): string {
|
|
59
|
+
return `'${escapeMinijinjaSingleQuoted(name)}'`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Python reserved words (the plan's list) that must not appear as a bare
|
|
64
|
+
* Jinja identifier — see the file header. Kept as the single source of
|
|
65
|
+
* truth, IDENTICAL to adapter-jinja's set; the Rust runtime's
|
|
66
|
+
* `mangle_ident` mirrors this exact set.
|
|
67
|
+
*/
|
|
68
|
+
const RESERVED_WORDS = new Set([
|
|
69
|
+
'if', 'else', 'for', 'in', 'is', 'not', 'and', 'or', 'none', 'true', 'false',
|
|
70
|
+
'import', 'from', 'class', 'def', 'pass', 'del', 'return', 'lambda', 'global',
|
|
71
|
+
'with', 'as', 'raise', 'try', 'except', 'finally', 'while', 'break',
|
|
72
|
+
'continue', 'elif', 'yield', 'assert', 'nonlocal',
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Mangle a JS identifier (prop name, signal getter, loop param, …) into a
|
|
77
|
+
* Jinja-safe variable name: reserved words get a trailing `_` suffix,
|
|
78
|
+
* everything else passes through unchanged. Applied at every point the
|
|
79
|
+
* adapter emits a bare Jinja variable reference or `{% set %}` target —
|
|
80
|
+
* mirrors Kolon's `$name` sigil giving those references collision immunity
|
|
81
|
+
* for free; Jinja has no sigil, so the adapter mangles instead.
|
|
82
|
+
*/
|
|
83
|
+
export function minijinjaIdent(name: string): string {
|
|
84
|
+
return RESERVED_WORDS.has(name) ? `${name}_` : name
|
|
85
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type aliases for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Near-verbatim port of `packages/adapter-jinja/src/adapter/lib/types.ts`
|
|
5
|
+
* (itself extracted the way `packages/adapter-xslate/src/adapter/lib/types.ts`
|
|
6
|
+
* is): pure type declarations — no runtime behaviour — so the extracted emit
|
|
7
|
+
* modules and the main adapter share one definition rather than
|
|
8
|
+
* re-declaring the render context / options shape. The emitted template
|
|
9
|
+
* syntax is identical to adapter-jinja's (both target Jinja2-compatible
|
|
10
|
+
* syntax); only the render engine (minijinja, Rust) and identity fields
|
|
11
|
+
* differ, so the type names below keep their `Jinja*` shape rather than
|
|
12
|
+
* being renamed per-file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** A template-primitive spec: expected call arity + the emit fn. */
|
|
16
|
+
export interface PrimitiveSpec {
|
|
17
|
+
arity: number
|
|
18
|
+
emit: (args: string[]) => string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The adapter's IRNode render context. Like the Xslate adapter, Jinja2's
|
|
23
|
+
* lowering doesn't consume any render-position flags, so the Ctx is empty.
|
|
24
|
+
* Kept as a named alias so future flags can extend it without changing the
|
|
25
|
+
* `IRNodeEmitter` interface.
|
|
26
|
+
*/
|
|
27
|
+
export type JinjaRenderCtx = Record<string, never>
|
|
28
|
+
|
|
29
|
+
export interface MinijinjaAdapterOptions {
|
|
30
|
+
/** Base path for client JS files (default: '/static/components/') */
|
|
31
|
+
clientJsBasePath?: string
|
|
32
|
+
|
|
33
|
+
/** Path to barefoot.js runtime (default: '/static/components/barefoot.js') */
|
|
34
|
+
barefootJsPath?: string
|
|
35
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-template memo / context seeding for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Near-verbatim port of `packages/adapter-jinja/src/adapter/memo/seed.ts`
|
|
5
|
+
* (itself ported from `packages/adapter-xslate/src/adapter/memo/seed.ts`).
|
|
6
|
+
* Free functions taking a `JinjaMemoContext` (built by the adapter's `memoCtx`
|
|
7
|
+
* getter) so the cluster depends only on the recursive expression entry, not
|
|
8
|
+
* the whole adapter class. These emit the `{% set x = ... %}` statements
|
|
9
|
+
* that let the body's bare `x` resolve to a derived signal/memo value or an
|
|
10
|
+
* active context value at SSR time.
|
|
11
|
+
*
|
|
12
|
+
* One deliberate behavioural IMPROVEMENT over the Kolon port: Kolon's `my`
|
|
13
|
+
* declares a NEW lexical, so `: my $x = … $x …;` reads the not-yet-assigned
|
|
14
|
+
* lexical on its own right-hand side — broken. Xslate therefore skips
|
|
15
|
+
* in-template seeding for a same-name signal/memo (a `refsSelf` guard in its
|
|
16
|
+
* `generateDerivedMemoSeed`). Jinja's `{% set %}` has no such shadowing
|
|
17
|
+
* hazard — `{% set x = x + 1 %}` resolves the right-hand `x` against the
|
|
18
|
+
* value ALREADY bound in the enclosing scope (verified empirically against
|
|
19
|
+
* Python's Jinja 3.1: `env.from_string("{% set x = x + 1 %}{{ x }}").render(x=5)`
|
|
20
|
+
* → `"6"`, not an error or a stale `5`; `{% set %}` inside `{% for %}` was
|
|
21
|
+
* independently re-verified against minijinja 2.21 by the orchestrator
|
|
22
|
+
* spike). This adapter therefore seeds a
|
|
23
|
+
* same-name signal/memo too, which is strictly more correct — the seed then
|
|
24
|
+
* re-derives from the already-bound prop instead of leaving the render var
|
|
25
|
+
* on its static (possibly per-callsite-wrong) default. No self-ref guard is
|
|
26
|
+
* ported for this reason.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
type ComponentIR,
|
|
31
|
+
type ContextConsumer,
|
|
32
|
+
collectContextConsumers,
|
|
33
|
+
computeSsrSeedPlan,
|
|
34
|
+
materializeGetterCalls,
|
|
35
|
+
} from '@barefootjs/jsx'
|
|
36
|
+
|
|
37
|
+
import type { JinjaMemoContext } from '../emit-context.ts'
|
|
38
|
+
import { extractTopLevelIdentifiers } from '../lib/ir-scope.ts'
|
|
39
|
+
import { minijinjaIdent, escapeMinijinjaSingleQuoted } from '../lib/minijinja-naming.ts'
|
|
40
|
+
|
|
41
|
+
/** Jinja literal for a context-consumer's `createContext` default. */
|
|
42
|
+
export function contextDefaultJinja(c: ContextConsumer): string {
|
|
43
|
+
const d = c.defaultValue
|
|
44
|
+
if (d === null || d === undefined) return 'none'
|
|
45
|
+
if (typeof d === 'string') return `'${escapeMinijinjaSingleQuoted(d)}'`
|
|
46
|
+
if (typeof d === 'boolean') return d ? 'true' : 'false'
|
|
47
|
+
return String(d)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Emit one `{% set <local> = bf.use_context(...) %}` statement per context
|
|
52
|
+
* consumer so the body's bare `<local>` resolves to the active provider
|
|
53
|
+
* value (or the `createContext` default). (#1297)
|
|
54
|
+
*/
|
|
55
|
+
export function generateContextConsumerSeed(ir: ComponentIR): string {
|
|
56
|
+
const consumers = collectContextConsumers(ir.metadata)
|
|
57
|
+
if (consumers.length === 0) return ''
|
|
58
|
+
return (
|
|
59
|
+
consumers
|
|
60
|
+
.map(
|
|
61
|
+
c =>
|
|
62
|
+
`{% set ${minijinjaIdent(c.localName)} = bf.use_context('${c.contextName}', ${contextDefaultJinja(c)}) %}`,
|
|
63
|
+
)
|
|
64
|
+
.join('\n') + '\n'
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Emit `{% set <name> = <jinja> %}` statements for every `derived` step of
|
|
70
|
+
* the backend-neutral SSR seed plan — the scope/availability/ordering
|
|
71
|
+
* analysis lives in `computeSsrSeedPlan` (packages/jsx/src/ssr-seed-plan.ts);
|
|
72
|
+
* this only lowers each step's expression to Jinja and applies the
|
|
73
|
+
* backend-specific emit guards: skip an empty lowering, and skip a lowering
|
|
74
|
+
* that references no top-level identifier at all (a constant init/body —
|
|
75
|
+
* e.g. a `derived` step with empty `frees` — keeps the existing static
|
|
76
|
+
* ssr-defaults seed instead). Unlike the Kolon port there is no self-ref
|
|
77
|
+
* guard — see the file header. `env-reader` and `opaque` steps emit nothing
|
|
78
|
+
* (the runtime supplies the reader, or the adapter's ssr-defaults path
|
|
79
|
+
* already covers it). (#1297, #2075)
|
|
80
|
+
*
|
|
81
|
+
* `convertExpressionToJinja` can still fail DEEPER than the plan's
|
|
82
|
+
* structural `isSupported` pre-check — e.g. a `.filter(predicate)` whose
|
|
83
|
+
* predicate isn't evaluator-serializable has NO lambda fallback on this
|
|
84
|
+
* adapter (divergence 3, `minijinja-adapter.ts`'s file header: unlike Kolon/Perl,
|
|
85
|
+
* Jinja has no lambda-expression form), so the lowering records a HARD
|
|
86
|
+
* BF101 as a side effect rather than degrading. That's correct for every
|
|
87
|
+
* OTHER `convertExpressionToJinja` call site (which commit to using the
|
|
88
|
+
* lowered text), but wrong here: this is a SPECULATIVE "recompute the memo
|
|
89
|
+
* in-template, else keep the static ssrDefault seed" attempt, so a deeper
|
|
90
|
+
* refusal must degrade silently, not fail the whole component compile.
|
|
91
|
+
* Snapshot the diagnostic list and roll back any errors appended during each
|
|
92
|
+
* step's attempt before moving on.
|
|
93
|
+
*
|
|
94
|
+
* A second, related Jinja-only gap divergence 3 opens up: a predicate
|
|
95
|
+
* referencing a SIBLING getter (`props.items.filter((p) => !tag() || …)`,
|
|
96
|
+
* `tag` a sibling memo) contains a zero-arg CALL node (`tag()`), which the
|
|
97
|
+
* evaluator's pure-expression surface refuses (`toEvalNode`'s `call` arm
|
|
98
|
+
* only allows a builtin callee, e.g. `Math.floor`) — with no lambda
|
|
99
|
+
* fallback to fall back to, the whole predicate would refuse. Kolon/Perl
|
|
100
|
+
* never hit this: their lambda form closes over the sibling's ALREADY-SEEDED
|
|
101
|
+
* lexical directly, without going through the evaluator at all. Go hits the
|
|
102
|
+
* identical gap (it also has no closures at SSR-constructor time) and fixes
|
|
103
|
+
* it the same way its `memo/memo-compute.ts` (`matchFilterArmMemo`) does:
|
|
104
|
+
* `materializeGetterCalls` rewrites a getter call into a bare identifier
|
|
105
|
+
* BEFORE serialization, so the evaluator captures it as a free-var read from
|
|
106
|
+
* `base_env` instead of an unsupported call node — and that free var then
|
|
107
|
+
* resolves against the sibling's own `{% set %}` line, which (being an
|
|
108
|
+
* EARLIER step per the plan's ordering guarantee) is always already bound.
|
|
109
|
+
* `env-reader` names are excluded from the materializable set — the runtime
|
|
110
|
+
* supplies those via the per-request reader, not a template-var lexical, so
|
|
111
|
+
* a call to one (`searchParams()`) must stay a real call, not a bare var.
|
|
112
|
+
*/
|
|
113
|
+
export function generateDerivedMemoSeed(ctx: JinjaMemoContext, ir: ComponentIR): string {
|
|
114
|
+
// Package G attached this to metadata at compile time; the `??` fallback
|
|
115
|
+
// only covers hand-built metadata in older tests that predate the attached
|
|
116
|
+
// plan — same shared function, so there's no divergence from the compiler.
|
|
117
|
+
const plan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata)
|
|
118
|
+
const knownGetterNames = new Set<string>(
|
|
119
|
+
plan.steps.filter(s => s.kind !== 'env-reader').map(s => s.name),
|
|
120
|
+
)
|
|
121
|
+
const lines: string[] = []
|
|
122
|
+
for (const step of plan.steps) {
|
|
123
|
+
if (step.kind !== 'derived') continue
|
|
124
|
+
const materialized = materializeGetterCalls(step.parsed, knownGetterNames)
|
|
125
|
+
const errorsBefore = ctx.errors.length
|
|
126
|
+
const jinja = ctx.convertExpressionToJinja('', materialized)
|
|
127
|
+
if (ctx.errors.length > errorsBefore) {
|
|
128
|
+
ctx.errors.length = errorsBefore
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (jinja === '' || extractTopLevelIdentifiers(jinja).length === 0) continue
|
|
132
|
+
lines.push(`{% set ${minijinjaIdent(step.name)} = ${jinja} %}`)
|
|
133
|
+
}
|
|
134
|
+
return lines.length > 0 ? lines.join('\n') + '\n' : ''
|
|
135
|
+
}
|