(This is a sponsored post.)
Of the languages that browsers speak, I’d wager that the very first one that developers decided needed some additional processing was HTML. Every single CMS in the world (aside from intentionally headless-only CMSs) is essentially an elaborate HTML processor: they take content and squoosh it together with HTML templates. There are dozens of other dedicated HTML processing languages that exist today.
The main needs of HTML processing being:
- Compose complete HTML documents from parts
- Template the HTML by injecting variable data
There are plenty of other features they can have, and we’ll get to that, but I think those are the biggies.
This research is brought to you by support from Frontend Masters, CSS-Tricks’ official learning partner.
Need front-end development training?
Frontend Masters is the best place to get it. They have courses on all the most important front-end technologies, from React to CSS, from Vue to D3, and beyond with Node.js and Full Stack.
Consider PHP. It’s literally a “Hypertext Preprocessor.” On this very website, I make use of PHP in order to piece together bits of templated HTML to build the pages and complete content you’re looking at now.
<h2> <?php the_title(); // Templating! ?>
</h2> <?php include("metadata.php"); // Partials! ?>
In the above code, I’ve squooshed some content into an HTML template, which calls another PHP file that likely contains more templated HTML. PHP covers the two biggies for HTML processing and is available with cost-friendly hosting — I’d guess that’s a big reason why PHP-powered websites power a huge chunk of the entire internet.
But PHP certainly isn’t the only HTML preprocessor around, and it requires a server to work. There are many others, some designed specifically to run during a build process before the website is ever requested by users.
Let’s go language-by-language and look at whether or not it supports certain features and how. When possible the link of the preprocessor name links to relevant docs.
Does it allow for templating?
Can you mix in data into the final HTML output?
Processor | Example |
---|---|
Pug | ✅- var title = "On Dogs: Man's Best Friend"; |
ERB | ✅<%= title %> |
Markdown | ❌ |
PHP | ✅<?php echo $post.title; ?> Also has HEREDOC syntax. |
Slim | ✅tr |
Haml | ✅<h1><%= post.title %></h1> |
Liquid | ✅Hello {{ user.name }}! |
Go html/template | ✅{{ .Title }} |
Handlebars | ✅{{firstname}} {{lastname}} |
Mustache | ✅Hello {{ firstname }}! |
Twig | ✅{{ foo.bar }} |
Nunjucks | ✅<h1>{{ title }}</h1> |
Kit | ✅<!-- $myVar = We finish each other's sandwiches. --> |
Sergey | ❌ |
Does it do partials/includes?
Can you compose HTML from smaller parts?
Processor | Example |
---|---|
Pug | ✅include includes/head.pug |
ERB | ✅<%= render 'includes/head' %> |
Markdown | ❌ |
PHP | ✅<?php include 'head.php'; ?> |
Slim | ⚠️ If you have access to the Ruby code, it looks like it can do it, but you have to write custom helpers. |
Haml | ✅.content |
Liquid | ✅{% render head.html %} |
Go html/template | ✅{{ partial "includes/head.html" . }} |
Handlebars | ⚠️ Only through registering a partial ahead of time. |
Mustache | ✅{{> next_more}} |
Twig | ✅{{ include('page.html', sandboxed = true) }} |
Nunjucks | ✅{% include "missing.html" ignore missing %} |
Kit | ✅<!-- @import "someFile.kit" --> |
Sergey | ✅<sergey-import src="header" /> |
Does it do local variables with includes?
As in, can you pass data to the include/partial for it to use specifically? For example, in Liquid, you can pass a second parameter of variables for the partial to use. But in PHP or Twig, there is no such ability—they can only access global variables.
Processor | Example |
---|---|
PHP | ❌ |
ERB | ✅<%= render( |
Markdown | ❌ |
Pug | ⚠️ Pug has mixins that you can put in a separate file and call. Not quite the same concept but can be used similarly. |
Slim | ❌ |
Haml | ✅.content |
Liquid | ✅{% render "name", my_variable: my_variable, my_other_variable: "oranges" %} |
Go html/template | ✅{{ partial "header/site-header.html" . }} (The period at the end is “variable scoping.”) |
Handlebars | ✅{{> myPartial parameter=favoriteNumber }} |
Mustache | ❌ |
Twig | ✅{% include 'template.html' with {'foo': 'bar'} only %} |
Nunjucks | ✅{% macro field(name, value='', type='text') %} |
Kit | ❌ |
Sergey | ❌ |
Does it do loops?
Sometimes you just need 100 <div>
s, ya know? Or more likely, you need to loop over an array 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 whatever you need to loop.
Processor | Example |
---|---|
PHP | ✅for ($i = 1; $i <= 10; $i++) { |
ERB | ✅<% for i in 0..9 do %> |
Markdown | ❌ |
Pug | ✅for (var x = 1; x < 16; x++) |
Slim | ✅- for i in (1..15) |
Haml | ✅(1..16).each do |i| |
Liquid | ✅{% for i in (1..5) %} |
Go html/template | ✅{{ range $i, $sequence := (seq 5) }} |
Handlebars | ✅{{#each myArray}} |
Mustache | ✅{{#myArray}} |
Twig | ✅{% for i in 0..10 %} |
Nunjucks | ✅{% set points = [0, 1, 2, 3, 4] %} |
Kit | ❌ |
Sergey | ❌ |
Does it have logic?
Mustache is famous for philosophically being “logic-less.” So sometimes it’s desirable to have a templating language that doesn’t mix in any other functionality, forcing you to deal with your business logic in another layer. Sometimes, a little logic is just what you need in a template. And actually, even Mustache has some basic logic.
Processor | Example |
---|---|
Pug | ✅#user |
ERB | ✅<% if show %> |
Markdown | ❌ |
PHP | ✅<?php if (value > 10) { ?> |
Slim | ✅- unless items.empty? If you turn on logic less mode:- article |
Haml | ✅if data == true |
Liquid | ✅{% if user %} |
Go html/template | ✅{{ if isset .Params "title" }} |
Handlebars | ✅{{#if author}} |
Mustache | ✅ It’s kind of ironic that Mustache calls itself “Logic-less templates”, but they do kinda have logic in the form of “inverted sections.” {{#repo}} |
Twig | ✅{% if online == false %} |
Nunjucks | ✅{% if hungry %} |
Kit | ✅ It can output a variable if it exists, which it calls “optionals”: <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. For example, escape special characters or capitalize text.
Processor | Example |
---|---|
Pug | ⚠️ Pug thinks of filters as ways to use other languages within Pug, and doesn’t ship with any out of the box. |
ERB | ✅ Whatever Ruby has, like: "hello James!".upcase #=> "HELLO JAMES!" |
Markdown | ❌ |
PHP | ✅$str = "Mary Had A Little Lamb"; |
Slim | ⚠️ Private only? |
Haml | ⚠️ Very specific one for whitespace removal. Mostly for embedding other languages? |
Liquid | ✅ Lots of them, and you can use multiple. {{ "adam!" | capitalize | prepend: "Hello " }} |
Go html/template | ⚠️ Has a bunch of functions, many of which are filter-like. |
Handlebars | ⚠️ Triple-brackets do HTML escaping, but otherwise, you’d have to register your own block helpers. |
Mustache | ❌ |
Twig | ✅{% autoescape "html" %} |
Nunjucks | ✅{% filter replace("force", "forth") %} |
Kit | ❌ |
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 that other language to do the math. Like Pug is written in JavaScript, so you can write JavaScript in Pug, which can do math.
Processor | Support |
---|---|
PHP | ✅<?php echo 1 + 1; ?> |
ERB | ✅<%= 1 + 1 %> |
Markdown | ❌ |
Pug | ✅- const x = 1 + 1 |
Slim | ✅- x = 1 + 1 |
Haml | ✅%p= 1 + 1 |
Liquid | ✅{{ 1 | plus: 1 }} |
Go html/template | ✅{{add 1 2}} |
Handlebars | ❌ |
Mustache | ❌ |
Twig | ✅{{ 1 + 1 }} |
Nunjucks | ✅{{ 1 + 1 }} |
Kit | ❌ |
Sergey | ❌ |
Does it have slots / blocks?
The concept of a slot is a template that has special areas within it that are filled with content should it be available. It’s conceptually similar to partials, but almost in reverse. Like you could think of a template with partials as the template calling those partials to compose a page, and you almost think of slots like a bit of data calling a template to turn itself into a complete page. Vue is famous for having slots, a concept that made its way to web components.
Processor | Example |
---|---|
PHP | ❌ |
ERB | ❌ |
Markdown | ❌ |
Pug | ✅ You can pull it off with “mixins” |
Slim | ❌ |
Haml | ❌ |
Liquid | ❌ |
Go html/template | ❌ |
Handlebars | ❌ |
Mustache | ❌ |
Twig | ✅{% block footer %} |
Nunjucks | ✅{% block item %} |
Kit | ❌ |
Sergey | ✅<sergey-slot /> |
Does it have a special HTML syntax?
HTML has <angle> <brackets> and while whitespace matters a little (a space is a space, but 80 spaces is also… a space), it’s not really a whitespace dependant language like Pug or Python. Changing these things up is a language choice. If all the language does is add in extra syntax, but otherwise, you write HTML as normal HTML, I’m considering that not a special syntax. If the language changes how you write normal HTML, that’s special syntax.
Processor | Example |
---|---|
PHP | ❌ |
ERB | In Ruby, if you want that you generally do Haml. |
Markdown | ✅ This is pretty much the whole point of Markdown. # Title |
Pug | ✅ |
Slim | ✅ |
Haml | ✅ |
Liquid | ❌ |
Go html/template | ❌ |
Handlebars | ❌ |
Mustache | ❌ |
Twig | ❌ |
Nunjucks | ❌ |
Kit | ⚠️ HTML comment directives. |
Sergey | ⚠️ Some invented HTML tags. |
Wait wait — what about stuff like React and Vue?
I’d agree that those technologies are component-based and used to do templating and often craft complete pages. They also can do many/most of the features 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, even if it sometimes feels like an afterthought (but not always). They also have other features that can be extremely compelling, like scoped/encapsulated styles, which requires cooperation between the HTML and CSS, which is an enticing feature.
I didn’t include them because they are generally intentionally used to essentially craft the DOM. They are focused on things like data retrieval and manipulation, state management, interactivity, and such. They aren’t really focused on just being an HTML processor. If you’re using a JavaScript framework, you probably don’t need a dedicated HTML processor, although it absolutely can be done. For example, mixing Markdown and JSX or mixing Vue templates and Pug.
I didn’t even put native web components on the list here because they are very JavaScript-focused.
Other considerations
- Speed — How fast does it process? Do you care?
- 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 build process? Or both?
Superchart
Templating | Includes | Local Variables | Loops | Logic | Filters | Math | Slots | Special Syntax | |
---|---|---|---|---|---|---|---|---|---|
PHP | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
ERB | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ⚠️ |
Markdown | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Pug | ✅ | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ |
Slim | ✅ | ⚠️ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ |
Haml | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ✅ |
Liquid | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Go html/template | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ❌ | ❌ |
Handlebars | ✅ | ⚠️ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
Mustache | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
Twig | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Nunjucks | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Kit | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ⚠️ |
Sergey | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ⚠️ |
The post Comparing HTML Preprocessor Features appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.