@barefootjs/jinja 0.31.8 → 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.
- package/dist/vite.js +63 -2
- package/package.json +5 -5
- package/python/barefootjs/runtime.py +14 -1
- package/src/test-render.ts +57 -9
package/dist/vite.js
CHANGED
|
@@ -3077,6 +3077,34 @@ var SVG_ROOT_TAGS = new Set([
|
|
|
3077
3077
|
"animateTransform",
|
|
3078
3078
|
"animateMotion"
|
|
3079
3079
|
]);
|
|
3080
|
+
var MATHML_ROOT_TAGS = new Set([
|
|
3081
|
+
"math",
|
|
3082
|
+
"mrow",
|
|
3083
|
+
"mfrac",
|
|
3084
|
+
"msup",
|
|
3085
|
+
"msub",
|
|
3086
|
+
"msubsup",
|
|
3087
|
+
"mn",
|
|
3088
|
+
"mi",
|
|
3089
|
+
"mo",
|
|
3090
|
+
"mtext",
|
|
3091
|
+
"munder",
|
|
3092
|
+
"mover",
|
|
3093
|
+
"munderover",
|
|
3094
|
+
"mtable",
|
|
3095
|
+
"mtr",
|
|
3096
|
+
"mtd",
|
|
3097
|
+
"msqrt",
|
|
3098
|
+
"mroot",
|
|
3099
|
+
"mstyle",
|
|
3100
|
+
"merror",
|
|
3101
|
+
"mpadded",
|
|
3102
|
+
"mphantom",
|
|
3103
|
+
"menclose",
|
|
3104
|
+
"semantics",
|
|
3105
|
+
"annotation",
|
|
3106
|
+
"annotation-xml"
|
|
3107
|
+
]);
|
|
3080
3108
|
|
|
3081
3109
|
// ../jsx/src/ir-to-client-js/collect-elements.ts
|
|
3082
3110
|
var EMPTY_RENDER_EXPRS = new Set(["null", "undefined", "false", "''", '""', "``"]);
|
|
@@ -3940,6 +3968,38 @@ function classify(name, origin, expr, parsed, available) {
|
|
|
3940
3968
|
}
|
|
3941
3969
|
return { kind: "derived", name, origin, expr, parsed, frees: [...frees] };
|
|
3942
3970
|
}
|
|
3971
|
+
function localConstExprsByName(metadata) {
|
|
3972
|
+
const out = new Map;
|
|
3973
|
+
for (const c of metadata.localConstants ?? []) {
|
|
3974
|
+
if (c.isModule || c.declarationKind !== "const" || c.value === undefined)
|
|
3975
|
+
continue;
|
|
3976
|
+
out.set(c.name, c.parsed ?? parseExpression(c.value.trim()));
|
|
3977
|
+
}
|
|
3978
|
+
return out;
|
|
3979
|
+
}
|
|
3980
|
+
function resolveThroughLocalConsts(parsed, localConsts) {
|
|
3981
|
+
let current = parsed;
|
|
3982
|
+
const maxIter = localConsts.size + 1;
|
|
3983
|
+
for (let i = 0;i < maxIter; i++) {
|
|
3984
|
+
const frees = freeIdentifiers(current);
|
|
3985
|
+
if (frees === null)
|
|
3986
|
+
break;
|
|
3987
|
+
let changed = false;
|
|
3988
|
+
for (const name of frees) {
|
|
3989
|
+
const value = localConsts.get(name);
|
|
3990
|
+
if (!value)
|
|
3991
|
+
continue;
|
|
3992
|
+
const inlined = inlineBinding(current, name, value);
|
|
3993
|
+
if (inlined === null)
|
|
3994
|
+
continue;
|
|
3995
|
+
current = inlined;
|
|
3996
|
+
changed = true;
|
|
3997
|
+
}
|
|
3998
|
+
if (!changed)
|
|
3999
|
+
break;
|
|
4000
|
+
}
|
|
4001
|
+
return current;
|
|
4002
|
+
}
|
|
3943
4003
|
function computeSsrSeedPlan(metadata) {
|
|
3944
4004
|
const baseScope = metadata.propsParams.map((p) => p.name);
|
|
3945
4005
|
if (metadata.propsObjectName)
|
|
@@ -3948,6 +4008,7 @@ function computeSsrSeedPlan(metadata) {
|
|
|
3948
4008
|
baseScope.push(name);
|
|
3949
4009
|
}
|
|
3950
4010
|
const available = new Set(baseScope);
|
|
4011
|
+
const localConsts = localConstExprsByName(metadata);
|
|
3951
4012
|
const steps = [];
|
|
3952
4013
|
for (const signal of metadata.signals) {
|
|
3953
4014
|
if (signal.envReader) {
|
|
@@ -3959,13 +4020,13 @@ function computeSsrSeedPlan(metadata) {
|
|
|
3959
4020
|
}
|
|
3960
4021
|
}
|
|
3961
4022
|
const expr = signal.initialValue.trim();
|
|
3962
|
-
steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify(signal.getter, "signal", expr, parseExpression(expr), available));
|
|
4023
|
+
steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify(signal.getter, "signal", expr, resolveThroughLocalConsts(parseExpression(expr), localConsts), available));
|
|
3963
4024
|
available.add(signal.getter);
|
|
3964
4025
|
}
|
|
3965
4026
|
for (const memo of metadata.memos) {
|
|
3966
4027
|
const body = extractArrowBodyExpression(memo.computation);
|
|
3967
4028
|
const expr = body?.trim() ?? "";
|
|
3968
|
-
steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify(memo.name, "memo", expr, memo.parsed ?? parseExpression(expr), available));
|
|
4029
|
+
steps.push(expr === "" ? { kind: "opaque", name: memo.name, origin: "memo" } : classify(memo.name, "memo", expr, resolveThroughLocalConsts(memo.parsed ?? parseExpression(expr), localConsts), available));
|
|
3969
4030
|
available.add(memo.name);
|
|
3970
4031
|
}
|
|
3971
4032
|
return { baseScope, steps };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jinja",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.10",
|
|
4
4
|
"description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/adapter-jinja"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.31.
|
|
56
|
+
"@barefootjs/shared": "0.31.10"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/jsx": ">=0.2.0",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
73
|
-
"@barefootjs/jsx": "0.31.
|
|
74
|
-
"@barefootjs/vite": "0.31.
|
|
75
|
-
"@barefootjs/client": "0.31.
|
|
73
|
+
"@barefootjs/jsx": "0.31.10",
|
|
74
|
+
"@barefootjs/vite": "0.31.10",
|
|
75
|
+
"@barefootjs/client": "0.31.10",
|
|
76
76
|
"typescript": "^5.0.0",
|
|
77
77
|
"vite": "^6.0.0"
|
|
78
78
|
}
|
|
@@ -612,11 +612,24 @@ class BarefootJS:
|
|
|
612
612
|
props = self._props()
|
|
613
613
|
if not props:
|
|
614
614
|
return ""
|
|
615
|
+
# Exclude the internal `scope_id` key from the client hydration
|
|
616
|
+
# payload: it is a server-side render detail (already carried by
|
|
617
|
+
# `_scope_id` for bf-s/bf-h/bf-m emission) with zero client runtime
|
|
618
|
+
# consumers -- the only bf-p parser
|
|
619
|
+
# (packages/client/src/runtime/hydrate.ts's parseProps -> runInit)
|
|
620
|
+
# never reads it back out. Filtered here, the single marshal
|
|
621
|
+
# boundary for bf-p, so it's excluded no matter how a `scope_id`
|
|
622
|
+
# key ends up in the props dict (found via the bf-p semantic-
|
|
623
|
+
# comparison audit against the Hono reference adapter, which never
|
|
624
|
+
# emits it).
|
|
625
|
+
client_props = {k: v for k, v in props.items() if str(k) != "scope_id"}
|
|
626
|
+
if not client_props:
|
|
627
|
+
return ""
|
|
615
628
|
# The JSON must be attribute-escaped: a raw `'` inside a string value
|
|
616
629
|
# (e.g. a blog paragraph) terminates the single-quoted attribute and
|
|
617
630
|
# truncates the hydration payload. The browser entity-decodes the
|
|
618
631
|
# attribute value, so the client's JSON.parse sees the original text.
|
|
619
|
-
j = _html_escape(self.backend.encode_json(
|
|
632
|
+
j = _html_escape(self.backend.encode_json(client_props))
|
|
620
633
|
return f" bf-p='{j}'"
|
|
621
634
|
|
|
622
635
|
# -----------------------------------------------------------------
|
package/src/test-render.ts
CHANGED
|
@@ -235,7 +235,7 @@ export async function renderJinjaComponent(options: RenderOptions): Promise<stri
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// Build props dict for Python.
|
|
238
|
-
const propsPy = buildPythonProps(componentName, props, ir)
|
|
238
|
+
const { propsPy, userPropsPy } = buildPythonProps(componentName, props, ir)
|
|
239
239
|
|
|
240
240
|
// Honour `__instanceId` from props for the root scope id so
|
|
241
241
|
// shared-component fixtures (which pin `<ComponentName>_test`) match
|
|
@@ -262,6 +262,18 @@ bf = BarefootJS(None, {'backend': backend})
|
|
|
262
262
|
bf._scope_id(${pyStr(rootScopeIdRaw)})
|
|
263
263
|
|
|
264
264
|
props = ${propsPy}
|
|
265
|
+
# Mirrors production's \`props_attr\` contract (see runtime.py's \`_props\`
|
|
266
|
+
# dual-accessor and its Ruby/Perl siblings): the caller is expected to
|
|
267
|
+
# seed \`_props\` before rendering the root component, so this harness
|
|
268
|
+
# must call it explicitly.
|
|
269
|
+
#
|
|
270
|
+
# \`userPropsPy\` — NOT \`props\` above — is what \`_props\` gets: \`props\` is
|
|
271
|
+
# this harness's internal vars hash (scope_id, signal/memo seed values,
|
|
272
|
+
# and — when the component imports searchParams — a non-JSON-serializable
|
|
273
|
+
# SearchParams reader object), none of which a real Flask route handler
|
|
274
|
+
# has or passes. Production's bf-p carries none of those, only the
|
|
275
|
+
# caller-facing props.
|
|
276
|
+
bf._props(${userPropsPy})
|
|
265
277
|
|
|
266
278
|
${childRenderers}
|
|
267
279
|
html = backend.render_named(${pyStr(toSnakeCase(componentName))}, bf, props)
|
|
@@ -480,17 +492,26 @@ function toSnakeCase(name: string): string {
|
|
|
480
492
|
|
|
481
493
|
/**
|
|
482
494
|
* Build a Python dict literal from props (+ signal / memo seeds).
|
|
495
|
+
*
|
|
496
|
+
* Returns the full internal vars-hash Python literal (`props`, what the
|
|
497
|
+
* compiled template renders against) AND `userPropsPy` — the exact raw
|
|
498
|
+
* fixture props (no defaults, no null-fill, no signal/memo seeding),
|
|
499
|
+
* mirroring production's route-handler call `bf._props(props)` verbatim.
|
|
500
|
+
* `userPropsPy` is for `bf._props(...)` (bf-p hydration payload) only —
|
|
501
|
+
* these must NOT be the same value, and `userPropsPy` must NOT be
|
|
502
|
+
* derived from `entries` or the defaulted stash: production's own bf-p
|
|
503
|
+
* carries only what the caller actually passed in, unmodified.
|
|
483
504
|
*/
|
|
484
505
|
function buildPythonProps(
|
|
485
506
|
_componentName: string,
|
|
486
507
|
props: Record<string, unknown> | undefined,
|
|
487
508
|
ir: ComponentIR,
|
|
488
|
-
): string {
|
|
509
|
+
): { propsPy: string; userPropsPy: string } {
|
|
510
|
+
// The full internal vars hash (local-template-var-name keyed, e.g.
|
|
511
|
+
// `count` for `{ n: count }`) that the compiled template renders
|
|
512
|
+
// against.
|
|
489
513
|
const entries: string[] = []
|
|
490
514
|
|
|
491
|
-
const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
|
|
492
|
-
entries.push(`${pyStr('scope_id')}: ${pyStr(explicitScope)}`)
|
|
493
|
-
|
|
494
515
|
// Prop params with defaults (before signals, so signals can reference them).
|
|
495
516
|
// Seeded through the shared `deriveStashFromDefaults` (the TS twin of the
|
|
496
517
|
// production `_derive_stash_from_defaults` this harness ALSO calls at
|
|
@@ -555,23 +576,50 @@ function buildPythonProps(
|
|
|
555
576
|
}
|
|
556
577
|
}
|
|
557
578
|
|
|
579
|
+
// `bf._props(...)` payload (bf-p hydration): mirrors production's
|
|
580
|
+
// route-handler call `bf._props(props)` verbatim — the caller's raw
|
|
581
|
+
// props dict, unmodified. No default-filling, no signal/memo seeding,
|
|
582
|
+
// no rest-bag nesting: a real Flask route handler never rearranges
|
|
583
|
+
// the dict it received before handing it to `_props`. Excludes
|
|
584
|
+
// internal harness-only keys (`__instanceId` etc.), which are never a
|
|
585
|
+
// real caller-facing prop.
|
|
586
|
+
const userEntries: string[] = []
|
|
587
|
+
if (props) {
|
|
588
|
+
for (const [key, value] of Object.entries(props)) {
|
|
589
|
+
if (key.startsWith('__')) continue
|
|
590
|
+
userEntries.push(`${pyStr(key)}: ${toPyLiteral(value)}`)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const userPropsPy = `{${userEntries.join(', ')}}`
|
|
594
|
+
|
|
595
|
+
const explicitScope = typeof props?.__instanceId === 'string' ? props.__instanceId : 'test'
|
|
596
|
+
const fullEntries = [...entries, `${pyStr('scope_id')}: ${pyStr(explicitScope)}`]
|
|
597
|
+
|
|
558
598
|
// Signal values evaluated from props (after user props).
|
|
559
599
|
for (const signal of ir.metadata.signals) {
|
|
560
600
|
// Env signals (#2057) are bound below via `SearchParams('')`, not from a
|
|
561
601
|
// static initial value.
|
|
562
602
|
if (signal.envReader) continue
|
|
603
|
+
// #2669: a self-derivation collision already got the correct RAW-prop
|
|
604
|
+
// seed above via `derivedProps` — `extractSsrDefaults` marks that with
|
|
605
|
+
// a `propName`-carrying entry (see its docstring's invariant). Skip
|
|
606
|
+
// re-seeding here or this harness's own `evaluateSignalInit` recompute
|
|
607
|
+
// clobbers the correctly-derived caller value.
|
|
608
|
+
if (rootSsrDefaults[signal.getter]?.propName !== undefined) continue
|
|
563
609
|
const value = evaluateSignalInit(signal.initialValue.trim(), props)
|
|
564
610
|
if (value !== null) {
|
|
565
|
-
|
|
611
|
+
fullEntries.push(`${pyStr(signal.getter)}: ${toPyLiteral(value)}`)
|
|
566
612
|
}
|
|
567
613
|
}
|
|
568
614
|
|
|
569
615
|
// Memo values seeded from the statically-evaluated ssrDefaults, same
|
|
570
616
|
// as the production plugin's before_render hook.
|
|
571
617
|
for (const memo of ir.metadata.memos) {
|
|
618
|
+
// #2669: same self-derivation skip as the signal loop above.
|
|
619
|
+
if (rootSsrDefaults[memo.name]?.propName !== undefined) continue
|
|
572
620
|
const entry = rootSsrDefaults[memo.name]
|
|
573
621
|
const value = entry ? entry.value : 0
|
|
574
|
-
|
|
622
|
+
fullEntries.push(`${pyStr(memo.name)}: ${toPyLiteral(value ?? 0)}`)
|
|
575
623
|
}
|
|
576
624
|
|
|
577
625
|
// (#1922) Request-scoped `searchParams()`: bind `searchParams` to an
|
|
@@ -581,10 +629,10 @@ function buildPythonProps(
|
|
|
581
629
|
// `.get(k)` resolves to `None` and the author's `?? default` renders. Only
|
|
582
630
|
// when the component imports `searchParams`.
|
|
583
631
|
if (importsSearchParams(ir.metadata)) {
|
|
584
|
-
|
|
632
|
+
fullEntries.push(`${pyStr('searchParams')}: SearchParams('')`)
|
|
585
633
|
}
|
|
586
634
|
|
|
587
|
-
return `{${
|
|
635
|
+
return { propsPy: `{${fullEntries.join(', ')}}`, userPropsPy }
|
|
588
636
|
}
|
|
589
637
|
|
|
590
638
|
|