BarefootJS

 view release on metacpan or  search on metacpan

lib/BarefootJS.pm  view on Meta::CPAN

# terminates it early, and &/</>/"/' are not special there. The key's exact
# text doesn't need to round-trip (the client's mapArrayAnchored matches
# items positionally and by its own JS-computed key, never by re-parsing
# the anchor Comment.nodeValue), so replacing every "-" with the
# visually-similar U+2010 is sufficient and needs no decoding.
sub escape_comment_key ($self, $value) {
    my $s = $self->string($value);
    $s =~ s/-/‐/g;
    return $s;
}

# ---------------------------------------------------------------------------
# JS-equivalent value stringification
# ---------------------------------------------------------------------------

# Map a Perl boolean-shaped value to the JS `String(bool)` form.
# Used by the Mojo adapter when emitting reactive attribute bindings
# whose JS source `isBooleanResultExpr` classified as boolean —
# a comparison (`count() > 0`), a logical negation (`!ok()`), or a
# literal `true` / `false`. Perl's auto-stringification of those
# expressions yields `''` / `1`; Hono and Go emit `'false'` / `'true'`.
# Centralising the bool → string mapping here keeps the contract
# testable and the template-emit syntax tidy
# (`<%= bf->bool_str(...) %>` vs an inline ternary).
#
# Contract is boolean-only: callers must have classified the
# expression as boolean-result before routing through this helper.
# Non-boolean values reaching here will be Perl-truthy-coerced to
# 'true' / 'false', which is generally wrong — non-boolean attribute
# bindings stay on the plain `<%= expr %>` emit path and never reach
# this function.
sub bool_str ($self, $value) {
    return $value ? 'true' : 'false';
}

sub text_start ($self, $slot_id) {
    return "<!--bf:$slot_id-->";
}

sub text_end ($self) {
    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;
}

# ---------------------------------------------------------------------------
# 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



( run in 1.440 second using v1.01-cache-2.11-cpan-b16cb0d3907 )