@barefootjs/mojolicious 0.15.2 → 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.
- package/dist/adapter/analysis/component-tree.d.ts +30 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +96 -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 +77 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/expr/operand.d.ts +34 -0
- package/dist/adapter/expr/operand.d.ts.map +1 -0
- package/dist/adapter/index.js +1490 -1334
- package/dist/adapter/lib/constants.d.ts +27 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +40 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/perl-naming.d.ts +29 -0
- package/dist/adapter/lib/perl-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +28 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +43 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/mojo-adapter.d.ts +46 -104
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/adapter/props/prop-classes.d.ts +48 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +64 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +26 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.js +1490 -1334
- package/dist/index.js +1490 -1334
- package/dist/test-render.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Mojo.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
- package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +163 -62
- package/src/__tests__/query-href.test.ts +94 -0
- package/src/adapter/analysis/component-tree.ts +128 -0
- package/src/adapter/emit-context.ts +107 -0
- package/src/adapter/expr/array-method.ts +408 -0
- package/src/adapter/expr/emitters.ts +607 -0
- package/src/adapter/expr/operand.ts +55 -0
- package/src/adapter/lib/constants.ts +39 -0
- package/src/adapter/lib/ir-scope.ts +70 -0
- package/src/adapter/lib/perl-naming.ts +36 -0
- package/src/adapter/lib/types.ts +31 -0
- package/src/adapter/memo/seed.ts +126 -0
- package/src/adapter/mojo-adapter.ts +230 -1476
- package/src/adapter/props/prop-classes.ts +87 -0
- package/src/adapter/spread/spread-codegen.ts +181 -0
- package/src/adapter/value/parsed-literal.ts +34 -0
- 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
|
+
}
|