Modules · Web · 0.13

Template::Stencil

A template engine that compiles once and then renders from C.

One {% %} grammar, compiled once to bytecode and rendered by a C interpreter straight into the scalar it returns. Templates are cached, includes revalidate on their own, and at steady state a render allocates nothing and makes no system call. Every page on this site is rendered by it.

use Template::Stencil;

my $stencil = Template::Stencil->new;

my $page = <<'TMPL';
<h1>{% title | upper %}</h1>
{% if items %}<ul>
{% for item in items %}  <li class="{% if loop.odd %}odd{% else %}even{% end %}">{% item.name %}: {% item.price | fmt('$%.2f') %}</li>
{% end %}</ul>{% else %}<p>Nothing yet.</p>{% end %}
TMPL

# compiled once, then rendered from the cache; values are escaped
print $stencil->render($page, {
    title => 'Menu',
    items => [ { name => 'Tea', price => 1.5 }, { name => 'Cake <3', price => 3 } ],
});

What you get

  • Compiled once

    File templates are cached and revalidated by modification time, string templates by a hash of their content. stat_ttl => -1 makes a steady-state render free of system calls.

  • A small grammar

    Output, filters, if/elsif/unless, for over arrays and hashes with loop.index and friends, set, include and a wrapper. Every block closes with {% end %}.

  • Escaped unless you say

    Output is HTML-escaped once, after the last filter, and raw opts out. Filters are C: upper, trim, uri, default, and fmt, whose format is checked when the template compiles.

  • Errors with a place

    Every error names the file, the line and, when compiling, the column. Under strict a missing value is an error that names its path.

  • Bytes ready to send

    A render returns UTF-8 bytes whose length is the Content-Length. The engine does the encoding, so the caller never does.

In use

use Template::Stencil;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
for ([ 'layout.tmpl', "<main>{% content %}</main>\n" ],
     [ 'card.tmpl',   '<b>{% user.name %}</b>' ],
     [ 'page.tmpl',   '{% for user in users %}{% include card %}{% end %}' ]) {
    open my $fh, '>', "$dir/$_->[0]" or die $!;
    print {$fh} $_->[1];
}

my $stencil = Template::Stencil->new(template_dir => $dir, wrapper => 'layout.tmpl', strict => 1);

print $stencil->render('page', { users => [ { name => 'ada' }, { name => 'tim' } ] });
# <main><b>ada</b><b>tim</b></main>

eval { $stencil->render('page', { users => [ {} ] }) };
print $@;   # names the file and line: card.tmpl:1: undef value for 'user.name'

For C

st_abi.h, resolved at run time through Template::Stencil::_abi_ptr: an XS module renders through the engine with no Perl frame between, and a template error comes back through an out-parameter rather than an exception. Punk renders its views this way.

Get it

cpanm Template::Stencil