Of the languages that browsers speak, I would bet that the very first that developers decided that further processing was needed was HTML. Every single one CMS in the world (except deliberately only headless CMSs) is essentially an elaborate HTML processor: they take content and squoosh it along with HTML templates. There are dozens of other dedicated HTML processing languages available today.
That most important HTML processing needs are:
- Compose complete HTML documents from parts
- Template HTML by injecting variable data
There are plenty of Other things features they can have and we’ll get to that, but I think they’s biggies.
This research comes to you with the support of Frontend Masters, CSS-Tricks’ official learning partner.
Do you need front-end development training?
Frontend Masters is the best place to get it. They have courses on all the major front-end technologies, from React to CSS, from Vue to D3 and on with Node.js and Full Stack.
Consider PHP. It’s literally a “Hypertext Preprocessor.” On this very site, I make use of PHP to put together snippets of template HTML to build the pages and complete content you are looking at now.
<h2>
<?php the_title(); // Templating! ?>
</h2>
<?php include("metadata.php"); // Partials! ?>
In the code above, I have squooshed some content into an HTML template, which calls another PHP file that probably contains more template HTML. PHP covers the two biggies of HTML processing and is available with cost-effective hosting — I guess that’s a big reason why PHP-powered websites run a huge portion of the entire Internet.
But PHP is certainly not the only HTML preprocessor, and it requires a server to work. There are many others, some designed specifically to run during a construction process Before the site is ever in demand by the users.
Let’s go language by language and see if it supports certain features and how. Whenever possible, link the preprocessor name to relevant documents.
Does it allow templates?
Can you mix data into the final HTML output?
Processor | Example |
---|---|
Pug | ✅- var title = "On Dogs: Man's Best Friend"; |
IS B | ✅<%= title %> |
Markdown | ❌ |
PHP | ✅<?php echo $post.title; ?> Also has HEREDOC syntax. |
Slender | ✅tr |
Haml | ✅<h1><%= post.title %></h1> |
Liquid | ✅Hello {{ user.name }}! |
Go to html / template | ✅{{ .Title }} |
Styr | ✅{{firstname}} {{lastname}} |
Moustache | ✅Hello {{ firstname }}! |
Twig | ✅{{ foo.bar }} |
Nunjucks | ✅<h1>{{ title }}</h1> |
Set | ✅<!-- $myVar = We finish each other's sandwiches. --> |
Sergey | ❌ |
Make it part / inclusive?
Can you compose HTML from smaller parts?
Processor | Example |
---|---|
Pug | ✅include includes/head.pug |
IS B | ✅<%= render 'includes/head' %> |
Markdown | ❌ |
PHP | ✅<?php include 'head.php'; ?> |
Slender | ⚠️ If you have access to the Ruby Code, it looks like it can do that, but you will need to enter custom helpers. |
Haml | ✅.content |
Liquid | ✅{% render head.html %} |
Go to html / template | ✅{{ partial "includes/head.html" . }} |
Styr | ⚠️ Only by registering a part in advance. |
Moustache | ✅{{> next_more}} |
Twig | ✅{{ include('page.html', sandboxed = true) }} |
Nunjucks | ✅{% include "missing.html" ignore missing %} |
Set | ✅<!-- @import "someFile.kit" --> |
Sergey | ✅<sergey-import src="https://css-tricks.com/comparing-html-preprocessor-features/header" /> |
Does it include local variables with include?
As in, can you pass on data to included / partial so that they can be used specifically? In Liquid you can e.g. Pass another parameter of variables that the partial must use. But in PHP or Twig there is no such capability – they can only access global variables.
Processor | Example |
---|---|
PHP | ❌ |
IS B | ✅<%= render( |
Markdown | ❌ |
Pug | ❌ |
Slender | ❌ |
Haml | ✅.content |
Liquid | ✅{% render "name", my_variable: my_variable, my_other_variable: "oranges" %} |
Go to html / template | ✅{{ partial "header/site-header.html" . }} (The period at the end is “variable scope.”) |
Styr | ✅{{> myPartial parameter=favoriteNumber }} |
Moustache | ❌ |
Twig | ❌ |
Nunjucks | ✅{% macro field(name, value="", type="text") %} |
Set | ❌ |
Sergey | ❌ |
Does it make loops?
Sometimes you just need 100 <div>
s, you know? Or more likely, you need to loop over a series of data and output HTML for each entry. There are lots of different types of loops, but having at least one is nice and you can generally make it work for what you need to loop.
Processor | Example |
---|---|
PHP | ✅for ($i = 1; $i <= 10; $i++) { |
IS B | ✅<% for i in 0..9 do %> |
Markdown | ❌ |
Pug | ✅for (var x = 1; x < 16; x++) |
Slender | ✅- for i in (1..15) |
Haml | ✅(1..16).each do |i| |
Liquid | ✅{% for i in (1..5) %} |
Go to html / template | ✅{{ range $i, $sequence := (seq 5) }} |
Styr | ✅{{#each myArray}} |
Moustache | ✅{{#myArray}} |
Twig | ✅{% for i in 0..10 %} |
Nunjucks | ✅{% set points = [0, 1, 2, 3, 4] %} |
Set | ❌ |
Sergey | ❌ |
Does it make sense?
Mustache is famous for being philosophically “logical”. So sometimes it is desirable to have a template language that does not interfere with any other functionality, forcing you to handle your business logic in a different layer. Sometimes a little logic is just what you need in a template. And in fact, even Mustache has some basic logic.
Processor | Example |
---|---|
Pug | ✅#user |
IS B | ✅<% if show %> |
Markdown | ❌ |
PHP | ✅<?php if (value > 10) { ?> |
Slender | ✅- unless items.empty? If you enable logical minor mode:- article |
Haml | ✅if data == true |
Liquid | ✅{% if user %} |
Go to html / template | ✅{{ if isset .Params "title" }} |
Styr | ✅{{#if author}} |
Moustache | ✅ It’s a little ironic that Mustache calls themselves “Logical Templates,” but they do little have logic in the form of “inverted sections.” {{#repo}} |
Twig | ✅{% if online == false %} |
Nunjucks | ✅{% if hungry %} |
Set | ✅ It can emit a variable, if it exists, which it calls “optional”: <dd class="<!-- $myVar? -->"> Page 1 </dd> |
Sergey | ❌ |
Does it have filters?
What I mean by filter here is a way to output content but change it on the way out. Avoid e.g. Special characters or use large text.
Processor | Example |
---|---|
Pug | ⚠️ Pug thinks of filters as ways to use other languages within Pug, and does not come with any out of the box. |
IS B | ✅ Whatever Ruby has, like: "hello James!".upcase #=> "HELLO JAMES!" |
Markdown | ❌ |
PHP | ✅$str = "Mary Had A Little Lamb"; |
Slender | ⚠️ Private only? |
Haml | ⚠️ Very specific for removing spaces. Mostly to integrate other languages? |
Liquid | ✅ Many of them and you can use more. {{ "adam!" | capitalize | prepend: "Hello " }} |
Go to html / template | ⚠️ Has a lot of features, many of which are filter-like. |
Styr | ⚠️ Triple parentheses escape HTML, but otherwise you have to register your own block helpers. |
Moustache | ❌ |
Twig | ✅{% autoescape "html" %} |
Nunjucks | ✅{% filter replace("force", "forth") %} |
Set | ❌ |
Sergey | ❌ |
Does it have math?
Sometimes math is baked right into the language. Some of these languages are built on top of other languages and thus use the other language to count. Just like Pug is written in JavaScript, so you can write JavaScript in Pug, which can do math.
Processor | Support |
---|---|
PHP | ✅<?php echo 1 + 1; ?> |
IS B | ✅<%= 1 + 1 %> |
Markdown | ❌ |
Pug | ✅- const x = 1 + 1 |
Slender | ✅- x = 1 + 1 |
Haml | ✅%p= 1 + 1 |
Liquid | ✅{{ 1 | plus: 1 }} |
Go to html / template | ✅{{add 1 2}} |
Styr | ❌ |
Moustache | ❌ |
Twig | ✅{{ 1 + 1 }} |
Nunjucks | ✅{{ 1 + 1 }} |
Set | ❌ |
Sergey | ❌ |
Does it have slots / blocks?
The concept of a castle is a template that has special areas in it that are filled with content if available. It looks conceptually partial, but almost in backwards. Just like you could think of a template with partials like the template that calls these partials to put together a page, and you almost think of slots as little data that calls a template to make itself a complete page. Vue is famous for having slots, a concept that found its way into web components.
Processor | Example |
---|---|
PHP | ❌ |
IS B | ❌ |
Markdown | ❌ |
Pug | ✅ You can pull it out with “mixins” |
Slender | ❌ |
Haml | ❌ |
Liquid | ❌ |
Go to html / template | ❌ |
Styr | ❌ |
Moustache | ❌ |
Twig | ✅{% block footer %} |
Nunjucks | ✅{% block item %} |
Set | ❌ |
Sergey | ✅<sergey-slot /> |
Does it have a special HTML syntax?
HTML has
Processor | Example |
---|---|
PHP | ❌ |
IS B | In Ruby, if you will, you generally do Haml. |
Markdown | ✅ This is pretty much the whole point of Markdown. # Title |
Pug | ✅ |
Slender | ✅ |
Haml | ✅ |
Liquid | ❌ |
Go to html / template | ❌ |
Styr | ❌ |
Moustache | ❌ |
Twig | ❌ |
Nunjucks | ❌ |
Set | ⚠️ HTML Comment Directives. |
Sergey | ⚠️ Some invented HTML tags. |
Wait wait – what about things like React and Vue?
I agree that these technologies are component-based and are used to create templates and often create complete pages. They can also do many / most of the functions listed here. Them and the many other JavaScript-based frameworks like them are also generally capable of running on a server or during a build step and producing HTML, although it sometimes feels like an afterthought (but not always). They also have other features that can be extremely compelling, such as scoped / encapsulated styles, which require collaboration between HTML and CSS, which is an enticing feature.
I did not include them because they are generally consciously used to essentially low DOM. They are focused on things like data retrieval and manipulation, governance, interactivity and such. They are not really focused on just being an HTML processor. If you use a JavaScript framework, you probably does not need a dedicated HTML processor, although it can definitely be done. For example, mixing Markdown and JSX or mixing Vue templates and Pug.
I did not even put native web components on the list here because they are very JavaScript focused.
Other considerations
- Velocity – How quickly is it treated? Are you worried?
- Language – What was in what is it written in? Is it compatible with the machines you need to support?
- Server or build – Does it require a web server running to work? Or can it be run once during a construction process? Or both?
Superchart
Template | Includes | Local variables | Loops | Logic | Filters | Mathematics | Slots | Special syntax | |
---|---|---|---|---|---|---|---|---|---|
PHP | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
IS B | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ⚠️ |
Markdown | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Pug | ✅ | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ |
Slender | ✅ | ⚠️ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ |
Haml | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ |
Liquid | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Go to html / template | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ❌ |
Styr | ✅ | ⚠️ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
Moustache | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
Twig | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Nunjucks | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Set | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ⚠️ |
Sergey | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ⚠️ |