@barefootjs/mojolicious 0.14.0 → 0.15.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/boolean-result.d.ts.map +1 -1
- package/dist/adapter/index.js +147 -47
- package/dist/adapter/mojo-adapter.d.ts +62 -10
- package/dist/adapter/mojo-adapter.d.ts.map +1 -1
- package/dist/build.js +147 -47
- package/dist/index.js +147 -47
- 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 +11 -1
- package/package.json +3 -3
- package/src/__tests__/mojo-adapter.test.ts +53 -4
- package/src/adapter/boolean-result.ts +5 -0
- package/src/adapter/mojo-adapter.ts +287 -39
- package/src/test-render.ts +98 -8
package/src/test-render.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Used by adapter-tests conformance runner.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { compileJSX, extractSsrDefaults } from '@barefootjs/jsx'
|
|
8
|
+
import { compileJSX, extractSsrDefaults, augmentInheritedPropAccesses, importsSearchParams } from '@barefootjs/jsx'
|
|
9
9
|
import type { ComponentIR } from '@barefootjs/jsx'
|
|
10
10
|
import { mkdir, rm } from 'node:fs/promises'
|
|
11
11
|
import { resolve } from 'node:path'
|
|
@@ -332,8 +332,12 @@ function buildChildRenderers(
|
|
|
332
332
|
lines.push(` close $child_fh;`)
|
|
333
333
|
lines.push(` my $child_mt = Mojo::Template->new(vars => 1, auto_escape => 1);`)
|
|
334
334
|
|
|
335
|
+
lines.push(` my $defaults_${snakeName} = ${buildChildDefaultsPerl(childIR)};`)
|
|
335
336
|
lines.push(` $bf->register_child_renderer('${snakeName}', sub {`)
|
|
336
|
-
|
|
337
|
+
// `$caller_bf` is the instance whose template invoked render_child
|
|
338
|
+
// (#1897) — nested children chain their scope identity off it.
|
|
339
|
+
lines.push(` my ($child_props, $caller_bf) = @_;`)
|
|
340
|
+
lines.push(` my $host_scope = (defined $caller_bf ? $caller_bf->_scope_id : $bf->_scope_id);`)
|
|
337
341
|
// (#1652) A child that destructures a rest-spread bag
|
|
338
342
|
// (`function NativeSelect({ children, ...props })`) emits a
|
|
339
343
|
// template referencing `$<restPropsName>`
|
|
@@ -349,8 +353,26 @@ function buildChildRenderers(
|
|
|
349
353
|
if (childIR.metadata.restPropsName) {
|
|
350
354
|
const rest = childIR.metadata.restPropsName
|
|
351
355
|
lines.push(` $child_props->{${rest}} = {} unless defined $child_props->{${rest}};`)
|
|
356
|
+
// (#1897) Route non-param props into the rest bag (JSX rest
|
|
357
|
+
// semantics): a caller prop the child didn't destructure belongs
|
|
358
|
+
// in the bag, not as a top-level stash var. Mirrors the Xslate
|
|
359
|
+
// harness and the production isRestProps branch.
|
|
360
|
+
const paramNames = (childIR.metadata.propsParams ?? []).map(p => p.name)
|
|
361
|
+
const keepList = [...new Set([...paramNames, rest, 'children', 'key', '_bf_slot'])]
|
|
362
|
+
.map(n => `'${n.replace(/[\\']/g, m => `\\${m}`)}'`)
|
|
363
|
+
.join(', ')
|
|
364
|
+
lines.push(` {`)
|
|
365
|
+
lines.push(` my %keep = map { $_ => 1 } (${keepList});`)
|
|
366
|
+
lines.push(` for my $k (grep { !$keep{$_} } keys %$child_props) {`)
|
|
367
|
+
lines.push(` $child_props->{${rest}}{$k} = delete $child_props->{$k};`)
|
|
368
|
+
lines.push(` }`)
|
|
369
|
+
lines.push(` }`)
|
|
352
370
|
}
|
|
353
371
|
lines.push(` my $child_bf = BarefootJS->new($c, {});`)
|
|
372
|
+
// (#1897) Nested `render_child` calls (a child template rendering
|
|
373
|
+
// another imported component) resolve against THIS instance's
|
|
374
|
+
// registry — share the parent's so they don't fail.
|
|
375
|
+
lines.push(` $child_bf->_child_renderers($bf->_child_renderers);`)
|
|
354
376
|
// JSX `key` (reserved prop) → data-key on the child's scope root, for
|
|
355
377
|
// keyed-loop reconciliation parity with Hono.
|
|
356
378
|
lines.push(` my $data_key = delete $child_props->{key};`)
|
|
@@ -362,11 +384,13 @@ function buildChildRenderers(
|
|
|
362
384
|
// `<ComponentName>_*`.
|
|
363
385
|
lines.push(` my $slot = delete $child_props->{_bf_slot};`)
|
|
364
386
|
lines.push(` if (defined $slot) {`)
|
|
365
|
-
lines.push(` $child_bf->_scope_id($
|
|
387
|
+
lines.push(` $child_bf->_scope_id($host_scope . "_$slot");`)
|
|
366
388
|
lines.push(` } else {`)
|
|
367
389
|
lines.push(` $child_bf->_scope_id('${componentName}_' . substr(rand() =~ s/^0\\.//r, 0, 6));`)
|
|
368
390
|
lines.push(` }`)
|
|
369
|
-
|
|
391
|
+
// Seed statically-derived defaults under the caller's props (#1897)
|
|
392
|
+
// so undeclared optional props / signals don't abort strict vars.
|
|
393
|
+
lines.push(` my $rendered = $child_mt->render($child_tmpl, { %$defaults_${snakeName}, %$child_props, bf => $child_bf });`)
|
|
370
394
|
lines.push(` die $rendered->to_string if ref $rendered;`)
|
|
371
395
|
lines.push(` chomp $rendered;`)
|
|
372
396
|
lines.push(` return $rendered;`)
|
|
@@ -391,6 +415,59 @@ function toSnakeCase(name: string): string {
|
|
|
391
415
|
/**
|
|
392
416
|
* Build a Perl hash literal from props.
|
|
393
417
|
*/
|
|
418
|
+
/**
|
|
419
|
+
* Statically-derived default template vars for a CHILD component
|
|
420
|
+
* (#1897): every declared prop (its JSX default or `undef`), inherited
|
|
421
|
+
* `props.<x>` accesses, signal initial values, and memo ssrDefaults.
|
|
422
|
+
* Without these, a child template referencing an optional prop the
|
|
423
|
+
* caller didn't pass (`$id`, `$className`) or its own signal (`$open`)
|
|
424
|
+
* aborts under Mojo::Template's strict vars. Caller-passed props win at
|
|
425
|
+
* merge time (`{ %$defaults, %$child_props }`). Mirrors the root-side
|
|
426
|
+
* seeding in `buildPerlProps` and the production plugin's
|
|
427
|
+
* `ssrDefaults` consumption.
|
|
428
|
+
*/
|
|
429
|
+
function buildChildDefaultsPerl(ir: ComponentIR): string {
|
|
430
|
+
// Surface inherited `props.<x>` reads hidden inside template-literal
|
|
431
|
+
// attr parts / conditional branches as propsParams (idempotent; the
|
|
432
|
+
// same shared pass the adapters apply — without it TabsContent's
|
|
433
|
+
// `${props.className ?? ''}` class read never seeds `$className`).
|
|
434
|
+
augmentInheritedPropAccesses(ir)
|
|
435
|
+
const entries: string[] = []
|
|
436
|
+
const declared = new Set<string>()
|
|
437
|
+
for (const param of ir.metadata.propsParams) {
|
|
438
|
+
declared.add(param.name)
|
|
439
|
+
if (param.defaultValue) {
|
|
440
|
+
const perlValue = jsToPerlValue(param.defaultValue)
|
|
441
|
+
if (perlValue !== null) {
|
|
442
|
+
entries.push(`${param.name} => ${perlValue}`)
|
|
443
|
+
continue
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
entries.push(`${param.name} => undef`)
|
|
447
|
+
}
|
|
448
|
+
if (ir.metadata.propsObjectName) {
|
|
449
|
+
for (const name of collectPropsObjectAccesses(ir, ir.metadata.propsObjectName)) {
|
|
450
|
+
if (declared.has(name)) continue
|
|
451
|
+
declared.add(name)
|
|
452
|
+
entries.push(`${name} => undef`)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const signal of ir.metadata.signals) {
|
|
456
|
+
const value = evaluateSignalInit(signal.initialValue.trim(), undefined)
|
|
457
|
+
entries.push(`${signal.getter} => ${value !== null ? toPerlLiteral(value) : 'undef'}`)
|
|
458
|
+
}
|
|
459
|
+
const ssrDefaults = extractSsrDefaults(ir.metadata) ?? {}
|
|
460
|
+
for (const memo of ir.metadata.memos) {
|
|
461
|
+
const entry = ssrDefaults[memo.name]
|
|
462
|
+
const value =
|
|
463
|
+
entry && typeof entry === 'object' && 'value' in entry ? entry.value : undefined
|
|
464
|
+
entries.push(
|
|
465
|
+
`${memo.name} => ${value !== undefined && value !== null ? toPerlLiteral(value) : 'undef'}`,
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
return `{${entries.join(', ')}}`
|
|
469
|
+
}
|
|
470
|
+
|
|
394
471
|
function buildPerlProps(
|
|
395
472
|
_componentName: string,
|
|
396
473
|
props: Record<string, unknown> | undefined,
|
|
@@ -515,12 +592,15 @@ function buildPerlProps(
|
|
|
515
592
|
}
|
|
516
593
|
}
|
|
517
594
|
|
|
518
|
-
// Add signal values evaluated from props (must come after user props)
|
|
595
|
+
// Add signal values evaluated from props (must come after user props).
|
|
596
|
+
// Seed `undef` for a null / unevaluable initial (e.g. a
|
|
597
|
+
// `createSignal<SortKey>(null)` whose getter is read in a child-prop
|
|
598
|
+
// ternary) rather than skipping it — an unseeded getter faults strict
|
|
599
|
+
// vars with `Global symbol "$x" requires explicit package name`. Same
|
|
600
|
+
// rule `buildChildDefaultsPerl` applies to child signals (#1897).
|
|
519
601
|
for (const signal of ir.metadata.signals) {
|
|
520
602
|
const value = evaluateSignalInit(signal.initialValue.trim(), props)
|
|
521
|
-
|
|
522
|
-
entries.push(`${signal.getter} => ${toPerlLiteral(value)}`)
|
|
523
|
-
}
|
|
603
|
+
entries.push(`${signal.getter} => ${value !== null ? toPerlLiteral(value) : 'undef'}`)
|
|
524
604
|
}
|
|
525
605
|
|
|
526
606
|
// Add memo values. The production Mojo plugin seeds these from the
|
|
@@ -536,6 +616,16 @@ function buildPerlProps(
|
|
|
536
616
|
entries.push(`${memo.name} => ${toPerlLiteral(value ?? 0)}`)
|
|
537
617
|
}
|
|
538
618
|
|
|
619
|
+
// (#1922) Request-scoped `searchParams()`: bind `$searchParams` to an
|
|
620
|
+
// empty-query reader via the lazy-loading factory (so the render script
|
|
621
|
+
// needn't `use BarefootJS::SearchParams`). The conformance harness issues no
|
|
622
|
+
// query string (the production Mojo plugin builds this from
|
|
623
|
+
// `$c->req->query_params`), so `.get(k)` resolves to undef and the author's
|
|
624
|
+
// `?? default` renders. Only when the component imports `searchParams`.
|
|
625
|
+
if (importsSearchParams(ir.metadata)) {
|
|
626
|
+
entries.push(`searchParams => BarefootJS->search_params('')`)
|
|
627
|
+
}
|
|
628
|
+
|
|
539
629
|
return `{${entries.join(', ')}}`
|
|
540
630
|
}
|
|
541
631
|
|