21

Comparing HTML Preprocessor Features | CSS-Tricks

 2 years ago
source link: https://css-tricks.com/comparing-html-preprocessor-features/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Comparing HTML Preprocessor Features

❥ Sponsored (Written by Chris Coyier)

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.

Diagram showing partials and {{ data }} turning into a complete HTML document.

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 .

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= title
p Written with love by #{author}
ERB
<%= title %>
<%= description %>

<%= @logged_in_user.name %>
Markdown❌PHP
<?php echo $post.title; ?>
<?php echo get_post_description($post.id) ?>
Also has HEREDOC syntax.Slim
tr
td.name = item.name
Haml
<h1><%= post.title %></h1>
<div class="subhead"><%= post.subtitle %></div>
Liquid
Hello {{ user.name }}!Go html/template
{{ .Title }}
{{ $address }}
Handlebars
{{firstname}} {{lastname}}Mustache
Hello {{ firstname }}!Twig
{{ foo.bar }}Nunjucks
<h1>{{ title }}</h1>Kit
<!-- $myVar = We finish each other's sandwiches. -->
<p> <!-- $myVar --> </p>
Sergey

Does it do partials/includes?

Can you compose HTML from smaller parts?

ProcessorExamplePug
include includes/head.pugERB
<%= render 'includes/head' %>Markdown❌PHP
<?php include 'head.php'; ?>
<?php include_once 'meta.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<!-- @import "someFile.kit" -->
<!-- @import "file.html" -->
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.

ProcessorExamplePHP❌ERB✅<%= render(
partial: "card",
locals: {
title: "Title"
}
) %>
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
= 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 }}MustacheTwig
{% include 'template.html' with {'foo': 'bar'} only %}Nunjucks✅{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}" value="{{ value | escape }}" />
</div>
{% endmacro %}
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.

ProcessorExamplePHPfor ($i = 1; $i <= 10; $i++) {
echo $i;
}
ERB<% for i in 0..9 do %>
<%= @users[i].name %>
<% end %>
Markdown❌Pugfor (var x = 1; x < 16; x++)
div= x
Slim- 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}}
<div class="row"></div>
{{/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#user
if user.description
h2.green Description
else if authorised
h2.blue Description
ERB<% if show %>
<% endif %>
Markdown❌PHP<?php if (value > 10) { ?>
<?php } ?>
Slim✅- unless items.empty?If you turn on logic less mode:
- article
h1 = title
-! article
p Sorry, article not found
Haml✅if data == true
%p true
else
%p false
Liquid{% if user %}
Hello {{ user.name }}!
{% endif %}
Go html/template{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>
{{ 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”:
<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.

ProcessorExamplePug⚠️
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";
$str = strtoupper($str);
echo $str; // Prints 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" %}
{{ var }}
{{ var|raw }} {# var won't be escaped #}
{{ var|escape }} {# var won't be doubled-escaped #}
{% endautoescape %}
Nunjucks{% filter replace("force", "forth") %}
may the force be with you
{% endfilter %}
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.

ProcessorSupportPHP<?php echo 1 + 1; ?>ERB✅<%= 1 + 1 %>Markdown❌Pug- const x = 1 + 1
p= x
Slim✅- x = 1 + 1
p= x
Haml✅%p= 1 + 1Liquid{{ 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 .

ProcessorExamplePHP❌ERB❌Markdown❌Pug✅
You can pull it off with “mixins”Slim❌Haml❌Liquid❌Go html/template❌Handlebars❌Mustache❌Twig{% block footer %}
© Copyright 2011 by you.
{% endblock %}
Nunjucks{% block item %}
The name of the item is: {{ item.name }}
{% endblock %}
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.

ProcessorExamplePHP❌ERBIn Ruby, if you want that you generally do Haml.Markdown✅
This is pretty much the whole point of Markdown.
# Title
Paragraph with [link](#link).

- List
- List

> Quote
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 ). 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 or mixing .

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

TemplatingIncludesLocal VariablesLoopsLogicFiltersMathSlotsSpecial SyntaxPHP✅✅❌✅✅✅✅❌❌ERB✅✅✅✅✅✅✅❌⚠️Markdown❌❌❌❌❌❌❌❌✅Pug✅✅❌✅✅⚠️✅✅✅Slim✅⚠️❌✅✅⚠️✅❌✅Haml✅✅✅✅✅⚠️✅❌✅Liquid✅✅✅✅✅✅✅❌❌Go html/template✅✅✅✅✅⚠️✅❌❌Handlebars✅⚠️✅✅✅❌❌❌❌Mustache✅✅❌✅✅❌❌❌❌Twig✅✅✅✅✅✅✅✅❌Nunjucks✅✅✅✅✅✅✅✅❌Kit✅✅❌❌✅❌❌❌⚠️Sergey❌✅❌❌❌❌❌✅⚠️

Comments

  1. Steve
    Permalink to comment# September 30, 2021

    Pug can do locals via mixins.

    • Chris Coyier
      Permalink to comment# September 30, 2021

      Here are the docs: https://pugjs.org/language/mixins.html

      It’s not clear to me that a mixin can be in a separate file – but can it?

    • Steve
      Permalink to comment# September 30, 2021

      Yes–mixins can reside in any arbitrary .pug file, you just need include it in the local .pug file. I like to keep a single mixins.pug to keep them in one place, so you only have to include one file with all the mixins.

  2. Callum
    Permalink to comment# September 30, 2021

    But you can pass local variables to Twig includes. That’s what the with keyword does:

    {% include "card.twig" with {title: "Title"} %}
    
  3. Daniel Sentker
    Permalink to comment# September 30, 2021

    Twig has local variables with includes.

    {{ include 'page.html' with {
       foo: 42
    } only }}
    
  4. Permalink to comment# September 30, 2021

    Does it do local variables with includes?

    Slim does everything haml/erb do. It can call any ruby method, including render.

    = render ...

    Another interesting thing with slim is you can write entire css/javascript/ruby blocks within a single file. Not unlike Vue’s SFC.

    I basically use this ability as poor-man’s Rails component. Combine with a helper method. You can even test it in isolation relatively easily.

  5. Sean Wright
    Permalink to comment# September 30, 2021

    You forgot ASP.NET Core’s Razor templating which has been around since 2009

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-5.0

    It’s cross platform, open source, and has all the features mentioned above, plus more

  6. D Rogatkin
    Permalink to comment# October 1, 2021

    Chris, it’s an amazing article. Thank you for the effort. But why did you call it as a preprocessor, and why HTML? Back to 2000, I did a preprocessor called as the template engine (https://github.com/drogatkin/aldan3/blob/master/src/java/org/aldan3/util/TemplateEngine.java) and it was multipurpose, you could generate any content including HTML. I still use it.

  7. Ampersand
    Permalink to comment# October 1, 2021

    Handlebars can do slots/blocks but the syntax is not super obvious

    {{!-- partial.hbs --}}
    <div>
        <header>
            {{> header}}
        </header>
        {{> @partial-block}}
        <footer>
            {{#> footer}}
                fallback content
            {{/footer}}
        </footer>
    </div>
    
    {{!-- template.hbs --}}
    {{#> partial}}
        {{#*inline 'header'}}
            foo
        {{/inline}}
        bar
    {{/partial}}
    
    {{!-- output --}}
    <div>
        <header>
            foo
        </header>
        bar
        <footer>
            fallback content
        </footer>
    </div>
    
  8. Permalink to comment# October 3, 2021

    Hope u can compare the SSR build ability for each.

    This is the part the many people are most interested in. ☺️

Leave a Reply Cancel reply

Comment

Name

Email

Website

Save my name, email, and website in this browser for the next time I comment.

Get the CSS-Tricks newsletter


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK