File Coverage

blib/lib/BarefootJS/Backend/Xslate.pm
Criterion Covered Total %
statement 59 63 93.6
branch 4 6 66.6
condition 2 5 40.0
subroutine 13 14 92.8
pod 5 6 83.3
total 83 94 88.3


line stmt bran cond sub pod time code
1             package BarefootJS::Backend::Xslate;
2             our $VERSION = "0.16.0";
3 1     1   182065 use strict;
  1         2  
  1         28  
4 1     1   3 use warnings;
  1         2  
  1         34  
5 1     1   4 use utf8;
  1         9  
  1         6  
6 1     1   23 use feature 'signatures';
  1         1  
  1         78  
7 1     1   4 no warnings 'experimental::signatures';
  1         1  
  1         31  
8              
9 1     1   576 use Text::Xslate ();
  1         9998  
  1         27  
10 1     1   621 use JSON::PP ();
  1         12879  
  1         388  
11              
12             # ---------------------------------------------------------------------------
13             # Text::Xslate (Kolon) rendering backend for the BarefootJS runtime.
14             # ---------------------------------------------------------------------------
15             #
16             # The engine-agnostic runtime logic — the JS-compat value helpers, array/string
17             # methods, hydration markers, child rendering — lives in BarefootJS
18             # (@barefootjs/perl). This backend supplies the four engine-specific operations
19             # the runtime delegates to, targeting Text::Xslate's Kolon syntax:
20             #
21             # encode_json($data) -> JSON string (injectable encoder)
22             # mark_raw($str) -> Text::Xslate raw value (no re-escaping)
23             # materialize($value) -> resolve a captured-children value to a string
24             # render_named($name, $bf, \%v) -> render `.tx` with `bf` + vars bound
25             #
26             # Pair it with the @barefootjs/xslate compile-time adapter, which emits Kolon
27             # `.tx` templates that call the runtime as a `$bf` object: `<: $bf.scope_attr()
28             # :>`, `<: $bf.json($x) :>`, `<: $bf.spread_attrs($bag) :>`. Kolon auto-escapes
29             # `<: ... :>` interpolations (`type => 'html'`); helpers that emit markup return
30             # `mark_raw` values (or the template adds `| mark_raw`), mirroring Mojo EP's
31             # `<%==` vs `<%=` distinction.
32             #
33             # Unlike the Mojo backend, this has no dependency on a web framework: a plain
34             # Text::Xslate instance renders templates from a path, so it runs under any
35             # PSGI / Plack app (or none at all).
36              
37 1     1 1 130799 sub new ($class, %args) {
  1         3  
  1         2  
  1         1  
38 1   33     6 my $json_encoder = $args{json_encoder} // do {
39             # Default pure-Perl encoder. `canonical` keeps key order deterministic
40             # (matching the runtime's sorted-key SSR policy); `allow_nonref` lets
41             # scalars / undef encode as `"x"` / `null`. Swap via `json_encoder`
42             # for a faster XS implementation.
43 1         7 my $j = JSON::PP->new->canonical->allow_nonref;
44 1     1   57 sub ($data) { return $j->encode($data) };
  1         5  
  1         1  
  1         2  
  1         2  
45             };
46              
47             # Accept a pre-built Text::Xslate instance, or build one from `path`
48             # (a dir of `.tx` templates) plus any extra `xslate_options`. The adapter
49             # calls every runtime helper as a `$bf` method (`$bf.filter`, `$bf.lc`, …)
50             # or a Kolon builtin (`.join`, `.size`), so no custom `function` map is
51             # needed here — a plain Kolon, html-escaping instance suffices.
52 1         2 my $xslate = $args{xslate};
53 1 50       3 unless ($xslate) {
54             $xslate = Text::Xslate->new(
55             syntax => 'Kolon',
56             type => 'html',
57             ($args{path} ? (path => $args{path}) : ()),
58 1 50 50     4 %{ $args{xslate_options} // {} },
  1         28  
59             );
60             }
61              
62 1         227 return bless { xslate => $xslate, json_encoder => $json_encoder }, $class;
63             }
64              
65 0     0 0 0 sub xslate ($self) { return $self->{xslate} }
  0         0  
  0         0  
  0         0  
66              
67 1     1 1 285 sub encode_json ($self, $data) {
  1         2  
  1         2  
  1         1  
68 1         4 return $self->{json_encoder}->($data);
69             }
70              
71             # Mark a string as already-safe so Kolon emits it verbatim (no auto-escape).
72 1     1 1 64496 sub mark_raw ($self, $str) {
  1         2  
  1         2  
  1         1  
73 1         17 return Text::Xslate::mark_raw($str);
74             }
75              
76             # JSX children captured by the adapter (a Kolon macro call yields a rendered
77             # string; some paths may pass a CODE ref) resolve to a string here.
78 2     2 1 5991 sub materialize ($self, $value) {
  2         4  
  2         3  
  2         4  
79 2 100       10 return ref($value) eq 'CODE' ? $value->() : $value;
80             }
81              
82             # Render `.tx` with `$child_bf` bound as the `bf` object for the nested
83             # render, plus the supplied template vars. No stash juggling is needed: Kolon
84             # resolves `$bf` from the per-render vars, so each child render gets its own
85             # instance directly.
86 1     1 1 41 sub render_named ($self, $template_name, $child_bf, $vars) {
  1         1  
  1         2  
  1         1  
  1         1  
  1         1  
87 1         11 return $self->{xslate}->render("$template_name.tx", { %$vars, bf => $child_bf });
88             }
89              
90             1;
91             __END__