Comparing HTML Preprocessor Features

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 partsTemplate the HTML by injecting variable dataThere 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.

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?
ProcessorExamplePug✅- var title = “On Dogs: Man’s Best Friend”;- var author = “enlore”;h1= titlep Written with love by #{author}ERB✅<%= title %><%= description %><%= %>Markdown❌PHP✅Also has HEREDOC syntax.Slim✅tr = item.nameHaml✅

<%= post.title %>

<%= post.subtitle %>

Liquid✅Hello {{ }}!Go html/template✅{{ .Title }}{{ $address }}Handlebars✅{{firstname}} {{lastname}}Mustache✅Hello {{ firstname }}!Twig✅{{ }}Nunjucks✅

{{ title }}


Sergey❌Does it do partials/includes?
Can you compose HTML from smaller parts?
ProcessorExamplePug✅include includes/head.pugERB✅<%= render 'includes/head' %>Markdown❌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=render ‘meeting_info’Liquid✅{% render head.html %}{% render meta.liquid %}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 %}{% import “forms.html” as forms %}{{ forms.label(‘Username’) }}Kit✅Sergey✅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.
ProcessorExamplePHP❌ERB✅<%= render(partial: "card",locals: {title: "Title"}) %>Markdown❌Pug❌Slim❌Haml✅.content= render :partial => ‘meeting_info’, :locals => { :info => @info }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❌Nunjucks✅{% macro field(name, value=””, type=”text”) %}

{% endmacro %}Kit❌Sergey❌Does it do loops?
Sometimes you just need 100

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.
ProcessorExamplePHP✅for ($i = 1; $i <= 10; $i++) {echo $i;}ERB✅<% for i in 0..9 do %><%= @users[i].name %><% end %>Markdown❌Pug✅for (var x = 1; x < 16; x++)div= xSlim✅- for i in (1..15)div #{i}Haml✅(1..16).each do |i|%div #{i}Liquid✅{% for i in (1..5) %}{% endfor %}Go html/template✅{{ range $i, $sequence := (seq 5) }}{{ $i }}: {{ $sequence }{{ end }}Handlebars✅{{#each myArray}}

{{/each}}Mustache✅{{#myArray}}{{name}}{{/myArray}}Twig✅{% for i in 0..10 %}{{ i }}{% endfor %}Nunjucks✅{% set points = [0, 1, 2, 3, 4] %}{% for x in points %}Point: {{ x }}{% endfor %}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.
ProcessorExamplePug✅#userif Descriptionelse if DescriptionERB✅<% if show %><% endif %>Markdown❌PHP✅ 10) { ?>Slim✅- unless items.empty?If you turn on logic less mode:- article h1 = title-! article p Sorry, article not foundHaml✅if data == true%p trueelse%p falseLiquid✅{% if user %}Hello {{ }}!{% endif %}Go html/template✅{{ if isset .Params “title” }}

{{ index .Params “title” }}

{{ end }}Handlebars✅{{#if author}}{{firstName}} {{lastName}}{{/if}}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}}{{name}}{{/repo}}{{^repo}}No repos :({{/repo}}Twig✅{% if online == false %}Our website is in maintenance mode.{% endif %}Nunjucks✅{% if hungry %}I am hungry{% elif tired %}I am tired{% else %}I am good!{% endif %}Kit✅It can output a variable if it exists, which it calls “optionals”:

Share your love

Leave a Reply