Perl has some great templating libraries for HTML. Most of them are pretty fast, and do what you want. Template::Toolkit is sort of the "gold standard", and you see it most often in the wild. Mojo::Template is the "newer" kid on the block, and is most associated with the Mojolicious framework/toolkit, if you're writing a Mojolicious application you're likely using Mojo::Template. There are also a few niche templating tools, like Text::Xslate (unfortunately, pretty much dead), and Template::Tiny (supported, but not seen very often). Like I said earlier, all of these libraries are great, you write templated text, and they give you HTML back, awesome. However, they all share a common "problem", they make me write HTML. I'm a Perl developer, I like writing Perl, I want to write Perl, so when I have to switch contexts to HTML it annoys me. To compound this problem, I just got off a project that used a tool called hiccup, hiccup let me write code in its native toungue (Clojure), and get back HTML in the form of a string. This rocked! Not only did I not have to write HTML, it was actually intuitive, it provided me with a DSL for HTML. So I took this idea, and ported it to Perl, in the form of HTML::Composer. HTML::Composer is a library that does its best to emulate that sort of intuitiveness, inside of Perl. To my surprise it seems to, and even more suprisingly, it performs incredibly well.
HTML::Composer only outputs HTML. It is a Perl data-structure to HTML converter, it takes an ARRAY ref, and outputs a string:
use HTML::Composer;
my $h = HTML::Composer->new();
my $html = $h->html([
head => [
title => ["My Site"],
script => {
src => "/js/myScript.js",
type => "text/javascript"
}
],
body => [
h1 => ["Hello World!"],
br => {},
div => { class => [ "p-3", "background-red" ] } => [
"Hello World!", h2 => ["Test 123"]
]
]
]
);
say $html; # <!DOCTYPE html><html> ...
You can also render partial HTML:
my $h = HTML::Composer->new;
my $html = $h->partial([
div => [
"Hello, World!",
a => { href => "https://www.google.com" } => ["www.google.com"]
]
]);
say $html; # <div>Hello, World!<a href="https://www.google.com"< ...
Since HTML::Composer operates inside of Perl data structures, and doesn't need to do any string parsing, it's quite fast. Faster than any other templating library outputting HTML I could find. The following benchmark is the average of five different one-hundred-million iteration runs rendering a medium sized HTML page that changed between iterations, with the caveat being, each of these libraries was expected to take an input scalar and output to a scalar variable (Text::Xslate is optimized to read from a file not a string, so it hated this benchmark):
Rate Text::Xslate Mojo::Template Template::Tiny Template::Toolkit HTML::Composer
Text::Xslate 433/s -- -78% -93% -93% -94%
Mojo::Template 1969/s 354% -- -69% -70% -72%
Template::Tiny 6410/s 1379% 226% -- -1% -9%
Template::Toolkit 6494/s 1398% 230% 1% -- -8%
HTML::Composer 7042/s 1525% 258% 10% 8% --
And, while being very quick, it also performs basic HTML validations to ensure you're sending something proper (it does not make sure the tags you pass are correct, this is very expensive). Instead of spending time parsing and building the HTML tree, the developer has already done the work, so all we need to do is compose it to HTML. It also performs the necessary escapes by default, if you need to avoid escaping, you can use the unsafe method
[ div => $h->unsafe("Stuff that shouldn't be escaped!"); ]
HTML::Composer uses a depth-first search, with a stack to traverse the HTML array you provide, based on the ordering of the data it makes assumptions about what you want, a string followed by a hash means you want to write a tag with some attributes, if that is then followed by an array, a tag with attributes and children. We verify if the tag is allowed to have children, then, we insert a reference to the current tag's scalar after the hash, and array. If a tag has no children, ie a tag with just a hash following, then we can finish that tag right now by pushing the HTML string output to the output array. When we encounter an array, simply push all of its elements onto the stack. Rinse and repeat until we encounter a scalar reference, and close the tag.
I learned Perl string concatenation is really slow. In the first pass at this project I used a
$root scalar to be the final output,
and concatenated the HTML in place, however after doing some r&d, I realised that pushing to an array, and then using Perl's join('', @array)
function, was almost one order of magnitude faster. The reason this is faster, is Perl's do_join function
is highly optimized, and seems to allocate the size of string it thinks it needs ahead of time, which saves a lot of CPU time.
The main logic tree uses ref and string eq's, I attempted to swap this to using regexp with a prefix
ref($foo) =~ /^SC/ # SCALAR, this is typically how I've optimized
logic in C code in the past, though, instead of regexp its referencing the bytes directly, which in retrospect is obviously going to be much faster than this.
However I did expect the prefix check with regexp to be at least slightly faster, but alas it's quite a bit slower.
Perl has great HTML templating libraries. I hate writing HTML so I wrote one that lets me stay in Perl, and export templates via sub-routines. It performs really well, which is to be expected, as it does no parsing of any actual text. I hope someone else with similar pains will try it out at some point. Thanks for reading!
Footnote: yes your eyes do not decieve you, that is codeberg, I'm going to be migrating my work off Github over the next couple weeks. AI was not involved in this project at all.