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 )