BarefootJS
view release on metacpan or search on metacpan
lib/BarefootJS.pm view on Meta::CPAN
}
sub render_child ($self, $name, @args) {
my $renderer = $self->_child_renderers->{$name};
die "No renderer registered for child component '$name'" unless $renderer;
# Accept both the Mojo list form â `bf->render_child($name, k => v, ...)`
# â and the single-hashref form â `$bf.render_child($name, { k => v })`.
# Template languages whose method calls can't splat a hash into positional
# args (Text::Xslate Kolon, Template Toolkit) pass one hashref instead.
my %props = (@args == 1 && ref $args[0] eq 'HASH') ? %{ $args[0] } : @args;
# JSX children come in via the engine's children-capture mechanism
# (Mojo's `begin %>...<% end`, which produces a CODE ref returning a
# Mojo::ByteStream). Materialize it through the backend before handing
# the props to the child renderer so the child template sees
# `$children` as already-rendered HTML. Guard on `exists` so a
# childless invocation (`bf->render_child('counter')`) doesn't gain a
# spurious `children => undef` key â preserving the historical "only
# touch children when present" behaviour.
$props{children} = $self->backend->materialize($props{children})
if exists $props{children};
# Renderer contract (#1897): the renderer is invoked with TWO
# arguments â the props hashref and the INVOKING instance. A renderer
# registered on the root may be called from a nested child render
# (AccordionTrigger -> ChevronDownIcon), and the grandchild's scope /
# slot identity must chain off the CALLER's scope id, not the
# registrant's. Renderers unpack `@_` (`my ($props, $caller) = @_;`)
# rather than enforcing arity with a one-arg subroutine signature â
# see `register_child_renderer`.
return $renderer->(\%props, $self);
}
# ---------------------------------------------------------------------------
# Bulk registration from build manifest
# ---------------------------------------------------------------------------
#
# `bf build` emits dist/templates/manifest.json describing every
# component the page might invoke (Counter, ui/button/index, ...).
# This helper walks that manifest and registers one child renderer per
# UI registry entry â the path shape `ui/<name>/index` maps to the
# `<name>` slot key Counter.html.ep and friends use via
# `<%= bf->render_child('<name>', ...) %>`.
#
# Each manifest entry carries an `ssrDefaults` hash derived statically
# from the component's JSX (prop destructure defaults + signal /
# memo initial values, see packages/jsx/src/ssr-defaults.ts). The
# child renderer seeds every template variable from that hash,
# preferring the caller's matching prop where one exists. This
# replaces the per-component `signal_init` callback that every
# scaffold's `app.pl` used to hand-roll for items 1/3 of issue #1416.
#
# `signal_init` remains as an opt-in override for cases the static
# extractor can't see through (e.g. signal initial values that
# reference imported helpers). When supplied for a given slot key
# it takes precedence over the manifest's `ssrDefaults` for that
# child, allowing callers to mix manual overrides with auto-derived
# defaults for siblings.
sub register_components_from_manifest ($self, $manifest, %opts) {
my $signal_inits = $opts{signal_init} // {};
my $parent_scope = $self->_scope_id;
# Weaken the parent capture so the child-renderer closures stored on
# `$self->_child_renderers` don't keep `$self` alive (the direct
# closure <-> parent cycle). The controller is reached through `$parent`
# at call time rather than captured strongly here, so the closures hold
# no strong reference to `$c` either â see the controller-cycle note in
# `new`. `$parent` is always live whenever a closure runs (the closure is
# stored on `$parent`, so `$parent` outlives every invocation).
weaken(my $parent = $self);
for my $entry_name (keys %$manifest) {
# `__barefoot__` is the runtime entry, not a component.
next if $entry_name eq '__barefoot__';
# Only UI registry components (path shape `ui/<name>/index`)
# become child renderers; top-level page components are the
# render target rather than a child.
next unless $entry_name =~ m{^ui/([^/]+)/index$};
my $slot_key = $1;
my $marked = $manifest->{$entry_name}{markedTemplate} // '';
next unless $marked;
# `templates/ui/button/index.html.ep` â `ui/button/index`
my $template_name = $marked;
$template_name =~ s{^templates/}{};
$template_name =~ s{\.html\.ep$}{};
my $signal_init = $signal_inits->{$slot_key};
my $manifest_defaults = $manifest->{$entry_name}{ssrDefaults};
$self->register_child_renderer($slot_key, sub {
# `$caller` is the instance whose template invoked
# `render_child` (#1897) â for a nested render that is a child
# instance, and the grandchild's scope/slot identity must chain
# off ITS scope id (`root_s0_s0`), not the registrant's.
my ($props, $caller) = @_;
my $host = $caller // $parent;
my $host_scope = $host->_scope_id // $parent_scope;
# Child shares the parent's backend so nested renders go
# through the same engine + controller (and inherit any
# injected json_encoder). The controller is fetched via the weak
# `$parent` at call time â never captured strongly â so the
# closure adds no edge to the per-request reference cycle.
my $child_bf = BarefootJS->new($parent->c, { backend => $parent->backend });
my $slot_id = delete $props->{_bf_slot};
# JSX `key` (a reserved prop) â data-key on the child's scope root
# for keyed-loop reconciliation (see `data_key_attr`).
my $data_key = delete $props->{key};
$child_bf->_data_key($data_key) if defined $data_key;
$child_bf->_scope_id(
$slot_id ? $host_scope . '_' . $slot_id
: $template_name . '_' . substr(rand() =~ s/^0\.//r, 0, 6)
);
$child_bf->_is_child(1);
# (#1249) Slot identity: host scope + slot id. Emitted as
# bf-h / bf-m attributes by hydration_attrs.
if ($slot_id) {
$child_bf->_bf_parent($host_scope);
$child_bf->_bf_mount($slot_id);
}
# Share the root registry so the child's own template can
# render further imported components (#1897).
$child_bf->_child_renderers($parent->_child_renderers);
$child_bf->_scripts($parent->_scripts);
$child_bf->_script_seen($parent->_script_seen);
( run in 1.574 second using v1.01-cache-2.11-cpan-df04353d9ac )