@barefootjs/mojolicious 0.17.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.
@@ -13,13 +13,10 @@ import {
13
13
  type ComponentIR,
14
14
  type ContextConsumer,
15
15
  collectContextConsumers,
16
- extractArrowBodyExpression,
17
- isSupported,
18
- parseExpression,
16
+ computeSsrSeedPlan,
19
17
  } from '@barefootjs/jsx'
20
18
 
21
19
  import type { MojoMemoContext } from '../emit-context.ts'
22
- import { referencedVarsAreAvailable } from '../lib/ir-scope.ts'
23
20
 
24
21
  /** Perl literal for a context-consumer's `createContext` default. */
25
22
  export function contextDefaultPerl(c: ContextConsumer): string {
@@ -49,78 +46,28 @@ export function generateContextConsumerSeed(ir: ComponentIR): string {
49
46
  }
50
47
 
51
48
  /**
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)
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)
61
59
  */
62
60
  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))
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)
69
65
  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)
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};`)
102
71
  }
103
72
  return lines.length > 0 ? lines.join('\n') + '\n' : ''
104
73
  }
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
- }
@@ -695,8 +695,15 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
695
695
  // ===========================================================================
696
696
 
697
697
  renderLoop(loop: IRLoop): string {
698
- // Client-only loops: skip SSR rendering entirely
699
- if (loop.clientOnly) return ''
698
+ // clientOnly loops must not render items at SSR time, but must still emit
699
+ // the `loop:`/`/loop:` boundary marker pair (Hono and Go parity) so the
700
+ // client runtime's mapArray() can locate the insertion anchor when
701
+ // hydrating the array. Without the markers, mapArray() resolves
702
+ // anchor = null and appends after sibling markers (#872). The marker id
703
+ // disambiguates sibling `.map()` calls under the same parent (#1087).
704
+ if (loop.clientOnly) {
705
+ return `<%== bf->comment("loop:${loop.markerId}") %><%== bf->comment("/loop:${loop.markerId}") %>`
706
+ }
700
707
 
701
708
  // An array/object-destructure loop param (`([emoji, users]) => ...`
702
709
  // or `({ name, age }) => ...`) lowers to invalid Perl — the adapter
@@ -1389,13 +1396,22 @@ export class MojoAdapter extends BaseAdapter implements IRNodeEmitter<MojoRender
1389
1396
  // now lowering `.length` on a higher-order object to
1390
1397
  // `scalar(@{...})`, the canonical
1391
1398
  // `x.tags.filter(t => t.active).length > 0` shape lowers
1392
- // cleanly. Predicates that combine a nested higher-order with
1393
- // something OTHER than `.length` (e.g. `.includes`, `.join`)
1394
- // still fall back to whatever the emitter produces most of
1395
- // those would yield runtime errors in Perl, which is the user's
1396
- // signal to refactor. Wholesale refusal would also block the
1397
- // canonical case the issue exists to enable.
1398
- return emitParsedExpr(expr, new MojoFilterEmitter(param, localVarMap, n => this._isStringValueName(n)))
1399
+ // cleanly, and nested `filter` / `every` / `some` lower to real
1400
+ // inline `grep` forms. The shapes the emitter can only DEGRADE
1401
+ // (nested `find*`, sort / reduce / flatMap inside a predicate)
1402
+ // surface BF101 through the hook below instead of silently
1403
+ // rewriting the predicate (#2038). Wholesale refusal would block
1404
+ // the canonical case #1443 exists to enable, so the gate lives at
1405
+ // the exact degrade points inside `MojoFilterEmitter.callbackMethod`.
1406
+ return emitParsedExpr(
1407
+ expr,
1408
+ new MojoFilterEmitter(
1409
+ param,
1410
+ localVarMap,
1411
+ n => this._isStringValueName(n),
1412
+ (message, reason) => this._recordExprBF101(message, reason),
1413
+ ),
1414
+ )
1399
1415
  }
1400
1416
 
1401
1417
  // ===========================================================================