@barefootjs/mojolicious 0.16.0 → 0.17.1
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 +85 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +78 -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 +1482 -1339
- 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 +33 -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 +34 -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 +1482 -1339
- package/dist/index.js +1482 -1339
- 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 +286 -69
- 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 +429 -0
- package/src/adapter/expr/emitters.ts +639 -0
- package/src/adapter/expr/operand.ts +55 -0
- package/src/adapter/lib/constants.ts +39 -0
- package/src/adapter/lib/ir-scope.ts +57 -0
- package/src/adapter/lib/perl-naming.ts +36 -0
- package/src/adapter/lib/types.ts +31 -0
- package/src/adapter/memo/seed.ts +73 -0
- package/src/adapter/mojo-adapter.ts +248 -1485
- 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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operand-type classification + index-access lowering for the Mojolicious
|
|
3
|
+
* EP template adapter.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
|
|
6
|
+
* track D). Pure functions over `ParsedExpr` — they take an `isStringName`
|
|
7
|
+
* predicate (supplied by the emitter from adapter state) rather than reading
|
|
8
|
+
* adapter instance state directly.
|
|
9
|
+
*
|
|
10
|
+
* SHARED CANDIDATE: `isStringTypedOperand` is byte-identical to the Xslate
|
|
11
|
+
* adapter's copy and is adapter-agnostic — an extraction candidate for a
|
|
12
|
+
* shared Perl-family codegen module (groundwork for the future Perl evaluator
|
|
13
|
+
* integration, issue #2018 track D). `emitIndexAccessPerl` stays Mojo-specific
|
|
14
|
+
* (Perl's `->[]` vs `->{}` split has no Kolon equivalent).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { ParsedExpr } from '@barefootjs/jsx'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether a comparison operand is string-typed, so JS `===`/`!==` against it
|
|
21
|
+
* must lower to Perl `eq`/`ne` instead of numeric `==`/`!=` (#1672). Covers a
|
|
22
|
+
* string literal, a string-signal getter call (`sel()`), and a string prop
|
|
23
|
+
* access (`props.x`). `isStringName` reports whether a getter/prop name is
|
|
24
|
+
* known-string. Loop-element fields (`t.id`) on untyped arrays have no known
|
|
25
|
+
* type and stay undetected — a separate, narrower gap.
|
|
26
|
+
*/
|
|
27
|
+
export function isStringTypedOperand(expr: ParsedExpr, isStringName: (n: string) => boolean): boolean {
|
|
28
|
+
if (expr.kind === 'literal' && expr.literalType === 'string') return true
|
|
29
|
+
if (expr.kind === 'call' && expr.callee.kind === 'identifier' && expr.args.length === 0) {
|
|
30
|
+
return isStringName(expr.callee.name)
|
|
31
|
+
}
|
|
32
|
+
if (expr.kind === 'member' && expr.object.kind === 'identifier' && expr.object.name === 'props') {
|
|
33
|
+
return isStringName(expr.property)
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lower `arr[index]` to a Perl deref. Perl distinguishes array
|
|
40
|
+
* (`->[$i]`) from hash (`->{$k}`) access, which JS's single `[]` does
|
|
41
|
+
* not — so we pick by the index expression's type: a string-typed key
|
|
42
|
+
* derefs the hash, anything else (the common loop-index / arithmetic
|
|
43
|
+
* case, e.g. `selected()[index]`) derefs the array. #1897.
|
|
44
|
+
*/
|
|
45
|
+
export function emitIndexAccessPerl(
|
|
46
|
+
object: ParsedExpr,
|
|
47
|
+
index: ParsedExpr,
|
|
48
|
+
emit: (e: ParsedExpr) => string,
|
|
49
|
+
isStringName: (n: string) => boolean,
|
|
50
|
+
): string {
|
|
51
|
+
const i = emit(index)
|
|
52
|
+
return isStringTypedOperand(index, isStringName)
|
|
53
|
+
? `${emit(object)}->{${i}}`
|
|
54
|
+
: `${emit(object)}->[${i}]`
|
|
55
|
+
}
|
|
@@ -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,57 @@
|
|
|
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
|
+
}
|
|
@@ -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,73 @@
|
|
|
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
|
+
computeSsrSeedPlan,
|
|
17
|
+
} from '@barefootjs/jsx'
|
|
18
|
+
|
|
19
|
+
import type { MojoMemoContext } from '../emit-context.ts'
|
|
20
|
+
|
|
21
|
+
/** Perl literal for a context-consumer's `createContext` default. */
|
|
22
|
+
export function contextDefaultPerl(c: ContextConsumer): string {
|
|
23
|
+
const d = c.defaultValue
|
|
24
|
+
if (d === null || d === undefined) return 'undef'
|
|
25
|
+
if (typeof d === 'string') return `'${d.replace(/[\\']/g, m => `\\${m}`)}'`
|
|
26
|
+
if (typeof d === 'boolean') return d ? '1' : '0'
|
|
27
|
+
return String(d)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Emit one `% my $<local> = bf->use_context(...)` seed line per context
|
|
32
|
+
* consumer so the template body's bare `$<local>` resolves to the active
|
|
33
|
+
* provider value (or the `createContext` default). (#1297)
|
|
34
|
+
*/
|
|
35
|
+
export function generateContextConsumerSeed(ir: ComponentIR): string {
|
|
36
|
+
const consumers = collectContextConsumers(ir.metadata)
|
|
37
|
+
if (consumers.length === 0) return ''
|
|
38
|
+
return (
|
|
39
|
+
consumers
|
|
40
|
+
.map(
|
|
41
|
+
c =>
|
|
42
|
+
`% my $${c.localName} = bf->use_context('${c.contextName}', ${contextDefaultPerl(c)});`,
|
|
43
|
+
)
|
|
44
|
+
.join('\n') + '\n'
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Emit `% my $<name> = <perl>;` seed lines for every `derived` step of the
|
|
50
|
+
* backend-neutral SSR seed plan — the scope/availability/ordering analysis
|
|
51
|
+
* lives in `computeSsrSeedPlan` (packages/jsx/src/ssr-seed-plan.ts); this
|
|
52
|
+
* only lowers each step's expression to Perl and applies the two
|
|
53
|
+
* backend-specific emit guards: skip an empty lowering, and skip a lowering
|
|
54
|
+
* that references no `$var` at all (a constant init/body — e.g. a `derived`
|
|
55
|
+
* step with empty `frees` — keeps the existing static ssr-defaults seed
|
|
56
|
+
* instead). `env-reader` and `opaque` steps emit nothing (the runtime
|
|
57
|
+
* supplies the reader, or the adapter's ssr-defaults path already covers it).
|
|
58
|
+
* (#1297, #2075)
|
|
59
|
+
*/
|
|
60
|
+
export function generateDerivedMemoSeed(ctx: MojoMemoContext, ir: ComponentIR): string {
|
|
61
|
+
// Package G attached this to metadata at compile time; the `??` fallback
|
|
62
|
+
// only covers hand-built metadata in older tests that predate the attached
|
|
63
|
+
// plan — same shared function, so there's no divergence from the compiler.
|
|
64
|
+
const plan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata)
|
|
65
|
+
const lines: string[] = []
|
|
66
|
+
for (const step of plan.steps) {
|
|
67
|
+
if (step.kind !== 'derived') continue
|
|
68
|
+
const perl = ctx.convertExpressionToPerl(step.expr, step.parsed)
|
|
69
|
+
if (perl === '' || !/\$[A-Za-z_]\w*/.test(perl)) continue
|
|
70
|
+
lines.push(`% my $${step.name} = ${perl};`)
|
|
71
|
+
}
|
|
72
|
+
return lines.length > 0 ? lines.join('\n') + '\n' : ''
|
|
73
|
+
}
|