@barefootjs/mojolicious 0.16.0 → 0.17.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.
Files changed (53) hide show
  1. package/dist/adapter/analysis/component-tree.d.ts +30 -0
  2. package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
  3. package/dist/adapter/emit-context.d.ts +96 -0
  4. package/dist/adapter/emit-context.d.ts.map +1 -0
  5. package/dist/adapter/expr/array-method.d.ts +75 -0
  6. package/dist/adapter/expr/array-method.d.ts.map +1 -0
  7. package/dist/adapter/expr/emitters.d.ts +77 -0
  8. package/dist/adapter/expr/emitters.d.ts.map +1 -0
  9. package/dist/adapter/expr/operand.d.ts +34 -0
  10. package/dist/adapter/expr/operand.d.ts.map +1 -0
  11. package/dist/adapter/index.js +1490 -1336
  12. package/dist/adapter/lib/constants.d.ts +27 -0
  13. package/dist/adapter/lib/constants.d.ts.map +1 -0
  14. package/dist/adapter/lib/ir-scope.d.ts +40 -0
  15. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  16. package/dist/adapter/lib/perl-naming.d.ts +29 -0
  17. package/dist/adapter/lib/perl-naming.d.ts.map +1 -0
  18. package/dist/adapter/lib/types.d.ts +28 -0
  19. package/dist/adapter/lib/types.d.ts.map +1 -0
  20. package/dist/adapter/memo/seed.d.ts +43 -0
  21. package/dist/adapter/memo/seed.d.ts.map +1 -0
  22. package/dist/adapter/mojo-adapter.d.ts +46 -104
  23. package/dist/adapter/mojo-adapter.d.ts.map +1 -1
  24. package/dist/adapter/props/prop-classes.d.ts +48 -0
  25. package/dist/adapter/props/prop-classes.d.ts.map +1 -0
  26. package/dist/adapter/spread/spread-codegen.d.ts +64 -0
  27. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  28. package/dist/adapter/value/parsed-literal.d.ts +26 -0
  29. package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
  30. package/dist/build.js +1490 -1336
  31. package/dist/index.js +1490 -1336
  32. package/dist/test-render.d.ts.map +1 -1
  33. package/lib/BarefootJS/Backend/Mojo.pm +1 -1
  34. package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
  35. package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
  36. package/package.json +3 -3
  37. package/src/__tests__/mojo-adapter.test.ts +124 -60
  38. package/src/__tests__/query-href.test.ts +94 -0
  39. package/src/adapter/analysis/component-tree.ts +128 -0
  40. package/src/adapter/emit-context.ts +107 -0
  41. package/src/adapter/expr/array-method.ts +408 -0
  42. package/src/adapter/expr/emitters.ts +607 -0
  43. package/src/adapter/expr/operand.ts +55 -0
  44. package/src/adapter/lib/constants.ts +39 -0
  45. package/src/adapter/lib/ir-scope.ts +70 -0
  46. package/src/adapter/lib/perl-naming.ts +36 -0
  47. package/src/adapter/lib/types.ts +31 -0
  48. package/src/adapter/memo/seed.ts +126 -0
  49. package/src/adapter/mojo-adapter.ts +223 -1476
  50. package/src/adapter/props/prop-classes.ts +87 -0
  51. package/src/adapter/spread/spread-codegen.ts +181 -0
  52. package/src/adapter/value/parsed-literal.ts +34 -0
  53. package/src/test-render.ts +2 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Compile-time constant tables for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D).
6
+ */
7
+
8
+ import type { PrimitiveSpec } from './types.ts'
9
+
10
+ /**
11
+ * Single source of truth for the Mojolicious adapter's
12
+ * template-primitive surface. Each entry pairs the expected arity
13
+ * with the emit function. Adding / removing a primitive is a
14
+ * one-line change.
15
+ *
16
+ * The emit fn returns a Perl expression (no surrounding `<%= %>`)
17
+ * suitable for embedding inside the Mojo template action —
18
+ * `bf->json($val)`, `bf->floor($val)`, etc. Args arrive already
19
+ * Perl-rendered via `convertExpressionToPerl` recursion, so a
20
+ * caller passing `props.config` reaches the emit fn as `$config`.
21
+ */
22
+ export const MOJO_TEMPLATE_PRIMITIVES: Record<string, PrimitiveSpec> = {
23
+ 'JSON.stringify': { arity: 1, emit: (args) => `bf->json(${args[0]})` },
24
+ 'String': { arity: 1, emit: (args) => `bf->string(${args[0]})` },
25
+ 'Number': { arity: 1, emit: (args) => `bf->number(${args[0]})` },
26
+ 'Math.floor': { arity: 1, emit: (args) => `bf->floor(${args[0]})` },
27
+ 'Math.ceil': { arity: 1, emit: (args) => `bf->ceil(${args[0]})` },
28
+ 'Math.round': { arity: 1, emit: (args) => `bf->round(${args[0]})` },
29
+ }
30
+
31
+ /**
32
+ * Module-scope `templatePrimitives` map derived once from the spec
33
+ * record. Per-instance derivation would re-build the same Map on
34
+ * every `new MojoAdapter()` call.
35
+ */
36
+ export const MOJO_PRIMITIVE_EMIT_MAP: Record<string, (args: string[]) => string> =
37
+ Object.fromEntries(
38
+ Object.entries(MOJO_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit])
39
+ )
@@ -0,0 +1,70 @@
1
+ /**
2
+ * IR traversal helpers for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Pure functions over the IR tree — no adapter instance state.
6
+ *
7
+ * SHARED CANDIDATE: the bodies here are byte-identical to the Xslate
8
+ * adapter's `lib/ir-scope.ts`. They are adapter-agnostic (no Perl/Kolon
9
+ * specifics), so they are the obvious first extraction into a shared
10
+ * Perl-family codegen module once one exists — the groundwork for the
11
+ * future Perl evaluator integration (issue #2018 track D). Kept per-adapter
12
+ * for now, matching the repo convention (the Go adapter keeps its own copy).
13
+ */
14
+
15
+ import type { IRNode, IRProp, IRIfStatement, IRFragment } from '@barefootjs/jsx'
16
+
17
+ /**
18
+ * Find the `children` prop's `jsx-children` payload (#1326). Narrowed
19
+ * via the AttrValue `kind` discriminator so adapter code stays type-
20
+ * safe if the IR shape evolves — adding a new AttrValue variant or
21
+ * renaming `children` to `jsxChildren` becomes a TS compile error
22
+ * here instead of silently dropping the children at runtime.
23
+ */
24
+ export function resolveJsxChildrenProp(props: readonly IRProp[]): IRNode[] {
25
+ const prop = props.find(p => p.name === 'children')
26
+ if (!prop) return []
27
+ if (prop.value.kind !== 'jsx-children') return []
28
+ return prop.value.children
29
+ }
30
+
31
+ /**
32
+ * Collect the component's root scope element node(s) — the elements that
33
+ * become the rendered root and so carry `data-key` for a keyed loop item. A
34
+ * plain element root is itself; an `if-statement` (early-return) root
35
+ * contributes the top element of each branch (`consequent` + the `alternate`
36
+ * chain), since exactly one branch renders at runtime. Non-element branch
37
+ * tops (fragments / nested shapes) are walked one level so an
38
+ * `if (…) return <A/>` still resolves to `<A>`. (#1297)
39
+ */
40
+ export function collectRootScopeNodes(node: IRNode): Set<IRNode> {
41
+ const out = new Set<IRNode>()
42
+ const visit = (n: IRNode | null): void => {
43
+ if (!n) return
44
+ if (n.type === 'element') { out.add(n); return }
45
+ if (n.type === 'if-statement') {
46
+ const s = n as IRIfStatement
47
+ visit(s.consequent)
48
+ visit(s.alternate)
49
+ return
50
+ }
51
+ if (n.type === 'fragment') {
52
+ for (const c of (n as IRFragment).children) visit(c)
53
+ }
54
+ }
55
+ visit(node)
56
+ return out
57
+ }
58
+
59
+ /**
60
+ * True when every `$var` the lowered (Perl / Kolon) expression references is
61
+ * in the available set — i.e. the template already has that var in scope.
62
+ * Guards in-template memo seeding from referencing an out-of-scope binding
63
+ * (which would trip Perl strict mode). (#1297)
64
+ */
65
+ export function referencedVarsAreAvailable(expr: string, available: ReadonlySet<string>): boolean {
66
+ for (const m of expr.matchAll(/\$([A-Za-z_]\w*)/g)) {
67
+ if (!available.has(m[1])) return false
68
+ }
69
+ return true
70
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Perl identifier / hash-key conventions for the Mojolicious EP adapter.
3
+ *
4
+ * Pure helpers extracted from `mojo-adapter.ts` (domain-module refactor,
5
+ * issue #2018 track D): none read adapter instance state, so they live at
6
+ * module scope as the single source of truth for Perl-identifier quoting
7
+ * and marker-id encoding.
8
+ */
9
+
10
+ /**
11
+ * (#checkbox) Quote a `render_child` named-arg / hashref key when it isn't a
12
+ * bare Perl identifier. A JSX attribute name like `data-slot` would otherwise
13
+ * emit `data-slot => '...'`, which Perl parses as the subtraction
14
+ * `data - slot`. Identifier-safe names (`className`, `size`, `_bf_slot`) pass
15
+ * through unquoted to keep the generated template readable.
16
+ */
17
+ export function perlHashKey(name: string): string {
18
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`
19
+ }
20
+
21
+ /**
22
+ * Encode an `IRLoop.markerId` into a Perl-identifier-safe suffix
23
+ * for the `bf_iter_…` hoist var. Collision-free for marker ids
24
+ * that differ in any character — `-` and `_` map to distinct
25
+ * encodings (`_x2d` vs `__`) so `l-0` and `l_0` stay distinct.
26
+ *
27
+ * Today the IR only emits `l<digits>` so the encoding is mostly
28
+ * an identity, but pinning collision-freeness up front avoids a
29
+ * silent variable-shadow bug if a future marker generator widens
30
+ * the alphabet.
31
+ */
32
+ export function perlIdentifierFromMarkerId(markerId: string): string {
33
+ return markerId.replace(/[^a-zA-Z0-9]/g, (ch) =>
34
+ ch === '_' ? '__' : `_x${ch.charCodeAt(0).toString(16)}`
35
+ )
36
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared type aliases for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Pure type declarations — no runtime behaviour — so the
6
+ * extracted emit modules and the main adapter share one definition
7
+ * rather than re-declaring the render context / options shape.
8
+ */
9
+
10
+ /** A template-primitive spec: expected call arity + the emit fn. */
11
+ export interface PrimitiveSpec {
12
+ arity: number
13
+ emit: (args: string[]) => string
14
+ }
15
+
16
+ /**
17
+ * Mojo adapter's IRNode render context. Mojo's lowering currently
18
+ * doesn't consume any render-position flags (`isRootOfClientComponent`
19
+ * is handled differently here than in Hono/Go), so the Ctx is empty.
20
+ * Kept as a named alias so future flags can extend it without changing
21
+ * the `IRNodeEmitter` interface.
22
+ */
23
+ export type MojoRenderCtx = Record<string, never>
24
+
25
+ export interface MojoAdapterOptions {
26
+ /** Base path for client JS files (default: '/static/components/') */
27
+ clientJsBasePath?: string
28
+
29
+ /** Path to barefoot.js runtime (default: '/static/components/barefoot.js') */
30
+ barefootJsPath?: string
31
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * In-template memo / context seeding for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Free functions taking a `MojoMemoContext` (built by the adapter's
6
+ * `memoCtx` getter) so the cluster depends only on the recursive expression entry, not
7
+ * the whole adapter class. These emit the `% my $x = ...;` seed lines that let
8
+ * the template body's bare `$x` resolve to a derived signal/memo value or an
9
+ * active context value at SSR time. Mirror of the Go adapter's `memo/*`.
10
+ */
11
+
12
+ import {
13
+ type ComponentIR,
14
+ type ContextConsumer,
15
+ collectContextConsumers,
16
+ extractArrowBodyExpression,
17
+ isSupported,
18
+ parseExpression,
19
+ } from '@barefootjs/jsx'
20
+
21
+ import type { MojoMemoContext } from '../emit-context.ts'
22
+ import { referencedVarsAreAvailable } from '../lib/ir-scope.ts'
23
+
24
+ /** Perl literal for a context-consumer's `createContext` default. */
25
+ export function contextDefaultPerl(c: ContextConsumer): string {
26
+ const d = c.defaultValue
27
+ if (d === null || d === undefined) return 'undef'
28
+ if (typeof d === 'string') return `'${d.replace(/[\\']/g, m => `\\${m}`)}'`
29
+ if (typeof d === 'boolean') return d ? '1' : '0'
30
+ return String(d)
31
+ }
32
+
33
+ /**
34
+ * Emit one `% my $<local> = bf->use_context(...)` seed line per context
35
+ * consumer so the template body's bare `$<local>` resolves to the active
36
+ * provider value (or the `createContext` default). (#1297)
37
+ */
38
+ export function generateContextConsumerSeed(ir: ComponentIR): string {
39
+ const consumers = collectContextConsumers(ir.metadata)
40
+ if (consumers.length === 0) return ''
41
+ return (
42
+ consumers
43
+ .map(
44
+ c =>
45
+ `% my $${c.localName} = bf->use_context('${c.contextName}', ${contextDefaultPerl(c)});`,
46
+ )
47
+ .join('\n') + '\n'
48
+ )
49
+ }
50
+
51
+ /**
52
+ * Seed memos whose SSR default is `null` (not statically evaluable) by
53
+ * computing them in-template from the already-seeded prop / signal vars.
54
+ * Targets the prop-derived memo shape (`createMemo(() => props.value * 10)`)
55
+ * that the static `extractSsrDefaults` evaluator can't fold — without this
56
+ * the memo's `$x` renders empty (the reason `props-reactivity-comparison`
57
+ * was skipped). Only emitted when the lowered expression references vars the
58
+ * template already has in scope (props params + signals + prior memos), so a
59
+ * memo over an out-of-scope binding stays on the null path rather than
60
+ * tripping Perl strict mode. (#1297)
61
+ */
62
+ export function generateDerivedMemoSeed(ctx: MojoMemoContext, ir: ComponentIR): string {
63
+ const memos = ir.metadata.memos ?? []
64
+ const signals = ir.metadata.signals ?? []
65
+ if (memos.length === 0 && signals.length === 0) return ''
66
+ // Props seed first; each signal/memo adds its own name as it lands so a
67
+ // later one can reference an earlier one.
68
+ const available = new Set<string>(ir.metadata.propsParams.map(p => p.name))
69
+ const lines: string[] = []
70
+
71
+ // Prop/signal-derived signals (`createSignal(props.defaultOn ?? false)`):
72
+ // a loop-child render receives no stash seed for the signal, so its `$on`
73
+ // would trip strict mode; and even when an entry render seeds it, the
74
+ // static default can't capture the per-call prop. Seed it in-template from
75
+ // the passed prop — but ONLY when the init lowers cleanly AND references an
76
+ // in-scope var (i.e. it's genuinely derived). Object/array/constant inits
77
+ // (`createSignal({…})`, `createSignal([…])`, `createSignal('b')`) keep the
78
+ // existing ssr-defaults seeding, so the spread / loop fixtures are
79
+ // untouched.
80
+ for (const signal of signals) {
81
+ const perl = tryLowerToPerl(ctx, signal.initialValue, available)
82
+ if (perl !== null) lines.push(`% my $${signal.getter} = ${perl};`)
83
+ available.add(signal.getter)
84
+ }
85
+
86
+ for (const memo of memos) {
87
+ // Seed every memo whose body lowers cleanly — not just the ones whose
88
+ // static SSR default is null. A statically-foldable prop-derived memo
89
+ // (`createMemo(() => props.disabled ?? false)` → default `false`)
90
+ // still depends on the per-call prop: the static stash seed bakes in
91
+ // the absent-prop fold, so a caller passing `disabled => 1` would
92
+ // render the default branch (#1897, select's disabled item). The
93
+ // in-template recomputation reads the prop lexical the stash already
94
+ // seeded, so it's correct per call; block-bodied arrows /
95
+ // out-of-scope references fall back to the static ssr-defaults seed.
96
+ const body = extractArrowBodyExpression(memo.computation)
97
+ if (body !== null) {
98
+ const perl = tryLowerToPerl(ctx, body, available)
99
+ if (perl !== null) lines.push(`% my $${memo.name} = ${perl};`)
100
+ }
101
+ available.add(memo.name)
102
+ }
103
+ return lines.length > 0 ? lines.join('\n') + '\n' : ''
104
+ }
105
+
106
+ /**
107
+ * Lower a signal init / memo body to Perl for an in-template SSR seed, or
108
+ * `null` when it shouldn't be seeded this way. Returns null — without
109
+ * recording a BF101 — when the expression isn't a supported shape
110
+ * (`isSupported` pre-check, so object/array literals don't fail the build),
111
+ * when the lowering references no in-scope var (a constant — keep the
112
+ * existing ssr-defaults seeding), or when it references an out-of-scope
113
+ * binding. (#1297)
114
+ */
115
+ export function tryLowerToPerl(
116
+ ctx: MojoMemoContext,
117
+ expr: string,
118
+ available: ReadonlySet<string>,
119
+ ): string | null {
120
+ const trimmed = expr.trim()
121
+ if (!trimmed) return null
122
+ if (!isSupported(parseExpression(trimmed)).supported) return null
123
+ const perl = ctx.convertExpressionToPerl(trimmed)
124
+ if (perl === '' || !/\$[A-Za-z_]\w*/.test(perl)) return null
125
+ return referencedVarsAreAvailable(perl, available) ? perl : null
126
+ }