BarefootJS
view release on metacpan or search on metacpan
lib/BarefootJS.pm view on Meta::CPAN
return "<!--/-->";
}
# See spec/compiler.md "Slot identity" for the comment-scope wire format.
sub scope_comment ($self) {
my $scope_id = $self->_scope_id // '';
my $host_segment = '';
my $host = $self->_bf_parent;
my $mount = $self->_bf_mount;
if (defined $host && length $host) {
$host_segment = "|h=$host|m=" . ($mount // '');
}
my $props_json = '';
if ($self->_props && %{$self->_props}) {
$props_json = '|' . $self->backend->encode_json($self->_props);
}
return "<!--bf-scope:$scope_id$host_segment$props_json-->";
}
# Paired end marker for scope_comment above. Bounds the scope's sibling
# range so client-side queries from a fragment-rooted scope don't leak
# onto later siblings owned by the parent (#2289). No `|h=`/`|m=`/props
# segments â the client only needs the scope id to find the matching end.
sub scope_comment_end ($self) {
my $scope_id = $self->_scope_id // '';
return "<!--bf-/scope:$scope_id-->";
}
# ---------------------------------------------------------------------------
# Script Registration
# ---------------------------------------------------------------------------
sub register_script ($self, $path) {
return if $self->_script_seen->{$path};
$self->_script_seen->{$path} = 1;
push @{$self->_scripts}, $path;
}
# Register a `<link rel="modulepreload">` hint â mirrors register_script
# exactly: same dedup-by-path hash, same insertion-order arrayref, same
# lifetime/reset (child-propagation) semantics, same no-output-string
# return so a template's `<: $bf->register_preload(...) :>` /
# `% $bf->register_preload(...);` emits no bytes where it sits. The
# `<link>` tag itself is only ever produced by `scripts` below, never
# here â a preload registration must never inject a node into a
# component's own template output.
sub register_preload ($self, $path) {
return if $self->_preload_seen->{$path};
$self->_preload_seen->{$path} = 1;
push @{$self->_preloads}, $path;
}
# ---------------------------------------------------------------------------
# SSR Portal Element Registration (#3119)
# ---------------------------------------------------------------------------
# Collects an `ssrPortalOwnerScope`-flagged element's already-rendered,
# fully-evaluated markup so it can be emitted at the `portals` outlet near
# `</body>` instead of at its source position â the `ref`-callback
# SSR-portal pattern the dialog-style primitives use
# (`DialogOverlay`/`DialogContent`, `DropdownMenuContent`,
# `PopoverContent`, the explicit `<Portal>` component). `$content` already
# carries its own `bf-po` attribute (stamped directly on its own tag by the
# compiler, matching exactly what the client
# `createPortal(el, document.body, { ownerScope })` stamps onto the SAME
# element at hydrate time), so unlike a hypothetical "wrap arbitrary
# children" collector this appends `$content` UNWRAPPED â no extra
# `bf-pi`/`bf-po` container element, which would diverge from what the
# client stamps directly onto the element itself. Returns nothing â every
# call site sinks the return value (a template's `% $bf->register_portal_
# element(...);` statement tag / Kolon `: $bf.register_portal_element(...)
# ;`, never an output tag), so nothing prints at the source position.
sub register_portal_element ($self, $content) {
push @{$self->_portal_elements}, $content;
return;
}
# Emits every collected SSR-portal element, in registration order. Place
# `<%= $bf->portals %>` (Mojo) / `: $bf.portals()` (Kolon) once near
# `</body>` in the app's own layout â mirrors `scripts` above (same
# "collect during render, emit at a single outlet" shape) and the Hono
# reference adapter's `<BfPortals />`.
sub portals ($self) {
return join "\n", @{$self->_portal_elements};
}
# ---------------------------------------------------------------------------
# Child Component Rendering
# ---------------------------------------------------------------------------
# (`_child_renderers` accessor is generated by the minimal accessor base above.)
# Register a renderer for `render_child($name, ...)`. The renderer is
# invoked as `$renderer->($props_hashref, $invoking_bf)` â unpack `@_`
# (`my ($props, $caller) = @_;`) instead of declaring a one-argument
# subroutine signature, which would enforce arity and die on the second
# argument.
sub register_child_renderer ($self, $name, $renderer) {
$self->_child_renderers->{$name} = $renderer;
}
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 AND any other named JSX-valued slot (`header={<strong/>}`,
# #2168 jsx-element-prop) come in via the engine's children-capture
# mechanism (Mojo's `begin %>...<% end`, which produces a CODE ref
# returning a Mojo::ByteStream). Materialize every prop value through
# the backend before handing the props to the child renderer, so the
# child template sees each slot as already-rendered HTML rather than a
# bare CODE ref â `materialize` is a no-op for a value that isn't a
# CODE ref (see e.g. `BarefootJS::Backend::Mojo::materialize`), so this
# is safe to apply unconditionally rather than naming `children`
# specifically.
$props{$_} = $self->backend->materialize($props{$_}) for keys %props;
# Renderer contract (#1897): the renderer is invoked with TWO
# arguments â the props hashref and the INVOKING instance. A renderer
( run in 1.891 second using v1.01-cache-2.11-cpan-062aa07a564 )