BarefootJS

 view release on metacpan or  search on metacpan

lib/BarefootJS.pm  view on Meta::CPAN

            }

            # Render the child template with $child_bf bound as the active
            # instance for the nested render. The backend owns the
            # engine-specific binding + restore (stash juggle for Mojo).
            my $html = $parent->backend->render_named(
                $template_name, $child_bf, { %$props, %extra },
            );
            chomp $html;
            return $html;
        });
    }
}

# Derive template-stash kvs from a manifest entry's `ssrDefaults`
# section. Each entry shape:
#   { value => <static-fallback>, propName => <prop>, isRestProps => bool }
# For `isRestProps`, the rest bag passes through unchanged (or the
# static `{}` if the caller didn't supply one). For ordinary entries
# the caller's `$props->{propName}` wins when defined, otherwise the
# static `value` does. `propName`-less entries (signal / memo locals)
# always use the static value — the caller cannot override them.
sub _derive_stash_from_defaults ($defaults, $props) {
    my %extra;
    for my $name (keys %$defaults) {
        my $d = $defaults->{$name};
        if (ref($d) ne 'HASH') {
            $extra{$name} = $d;
            next;
        }
        if ($d->{isRestProps}) {
            $extra{$name} = exists $props->{$name} ? $props->{$name} : $d->{value};
            next;
        }
        my $prop_name = $d->{propName};
        if (defined $prop_name && exists $props->{$prop_name} && defined $props->{$prop_name}) {
            $extra{$name} = $props->{$prop_name};
        } else {
            $extra{$name} = $d->{value};
        }
    }
    return %extra;
}

# ---------------------------------------------------------------------------
# Script Output
# ---------------------------------------------------------------------------

sub scripts ($self) {
    my @tags;
    for my $path (@{$self->_scripts}) {
        push @tags, qq{<script type="module" src="$path"></script>};
    }
    return join("\n", @tags);
}

# ---------------------------------------------------------------------------
# Streaming SSR (Out-of-Order)
# ---------------------------------------------------------------------------

sub streaming_bootstrap ($self) {
    return q{<script>(function(){function s(id){var a=document.querySelector('[bf-async="'+id+'"]');var t=document.querySelector('template[bf-async-resolve="'+id+'"]');if(!a||!t)return;a.replaceChildren(t.content.cloneNode(true));a.removeAttribute('b...
}

sub async_boundary ($self, $id, $fallback_html) {
    # The fallback comes in via Mojo `begin %>...<% end` capture (see
    # MojoAdapter::renderAsync), which produces a CODE ref returning a
    # Mojo::ByteStream. Materialize it through the backend so the rendered
    # HTML embeds in the placeholder rather than the CODE ref's
    # stringification.
    $fallback_html = $self->backend->materialize($fallback_html);
    return qq{<div bf-async="$id">$fallback_html</div>};
}

sub async_resolve ($self, $id, $content_html) {
    return qq{<template bf-async-resolve="$id">$content_html</template><script>__bf_swap("$id")</script>};
}

# ---------------------------------------------------------------------------
# JS-compat callees (#1189) — invoked from generated Mojo templates as
# <%= bf->json($val) %>, <%= bf->floor($val) %>, etc. The MojoAdapter's
# `templatePrimitives` registry emits these helper calls in place of the
# corresponding JS callees (`JSON.stringify`, `Math.floor`, …) so the SSR
# template can render value-equivalent output without a JS engine.
#
# Failure policy mirrors the Go adapter (#1188): user-data marshalling
# (json) bubbles errors so Mojolicious aborts loudly on cycles /
# unsupported values rather than silently producing an empty payload.
# Numeric coercion follows JS semantics (NaN propagates as the special
# string 'NaN'; non-numeric input returns 'NaN' rather than 0). Strings
# always coerce to a string representation.
# ---------------------------------------------------------------------------

sub json ($self, $value) {
    # Mojo::JSON::to_json returns a character string (not bytes), suitable
    # for embedding in HTML output via Mojo::ByteStream / `<%==`.
    #
    # Documented divergence from JS: JS distinguishes `null` (renders as
    # "null") from `undefined` (`JSON.stringify(undefined)` returns the
    # JS value `undefined`, not a string). Perl has no such distinction
    # — both map to `undef`. We choose the `null` rendering for SSR
    # ergonomics: an unset prop becomes the string "null" rather than
    # the literal text "undefined" or an empty attribute. Matches the
    # `null` case of JS exactly; diverges from the `undefined` case.
    return $self->backend->encode_json($value);
}

sub string ($self, $value) {
    # JS `String(v)` mirror. `undef` renders as the empty string here so
    # an unset prop doesn't surface as a literal "undefined" / "null"
    # in user-facing HTML — same divergence the Go adapter documents
    # for `bf_string`.
    return defined $value ? "$value" : '';
}

sub number ($self, $value) {
    # JS `Number(v)` mirror. Numeric coerces via Perl's implicit
    # numeric context; non-numeric / undef yield real numeric NaN
    # (`'nan' + 0`) so downstream arithmetic propagates correctly
    # (`Math.floor(NaN) === NaN`). Returning the literal string
    # "NaN" would conflate the user-passing-the-string-"NaN" case



( run in 1.839 second using v1.01-cache-2.11-cpan-140bd7fdf52 )