HTML Preprocessors, Compared

HTML is arguably the first language developers decided needed preprocessing. Every traditional CMS is essentially an elaborate HTML processor — they take content and merge it with HTML templates. But dedicated HTML preprocessing languages exist in abundance, and they vary widely in what they offer.

The core needs are straightforward: composing complete documents from parts, and templating by injecting variable data. Beyond that, features diverge across languages.

A Field of Preprocessors

Consider PHP — literally a "Hypertext Preprocessor." It handles composition and templating well, runs on cost-friendly hosting, and powers a substantial share of the web. But PHP requires a server. Plenty of other preprocessors are designed to run once during a build step, before users ever request the page.

The field includes familiar names like Haml, Pug, Slim, Twig, Liquid, Mustache, Handlebars, and others. Each brings its own philosophy and feature set to the problem of turning templates into HTML.

Below, language-by-language, is a breakdown of whether each supports certain features — and how.

Templating

The baseline: can you mix variable data into the final HTML output? Most preprocessors answer yes, though approaches differ. Some use mustache-style {{ }} tags, others use language-specific delimiters, and some — like Pug — inherit the expression syntax of their host language.

Partials and Includes

Composing pages from smaller HTML fragments is nearly universal. PHP does it natively with include. Liquid and Twig both support include/partial constructs. The syntax varies, but the concept — a document that pulls in reusable chunks — is consistent across the board.

Local Variables with Includes

This is trickier. Passing data to an included partial for its own use is supported in Liquid, which accepts a second parameter of variables. PHP and Twig don't offer this; partials in those languages can only access globally scoped variables.

Loops

When you need 100 <div>s — or, more realistically, an array of data rendered as repeated HTML — a loop construct matters. Nearly every preprocessor has at least one. The types vary, but having any loop generally covers what you need.

Logic

Mustache is famous for being philosophically "logic-less," forcing business logic into another layer. But even Mustache has some basic logic built in. Others embrace conditionals and branching directly in templates. It's a spectrum: logic-less simplicity versus inline convenience.

Filters

A filter transforms content on its way out — escaping special characters, capitalizing text, formatting dates. Liquid and Twig ship rich filter libraries. Others delegate this to the host language or provide minimal built-ins.

Math

Some preprocessors bake math in directly. Others inherit it from the language they're built on. Since Pug is written in JavaScript, you can drop JavaScript expressions into Pug templates, which naturally includes arithmetic.

Slots and Blocks

Slots represent an inverse of partials. With partials, the template calls out to compose the page. With slots, content flows into predefined areas within a template. Vue popularized slots, and the concept has since made its way into web components. Support varies among traditional HTML preprocessors.

Special HTML Syntax

HTML's angle brackets and forgiving whitespace are one option. Languages like Pug and Haml deliberately change the syntax — indentation matters, and tags are written without brackets. If a language only adds extra tags but leaves HTML as-is, that doesn't count as special syntax. Changing how you write basic HTML does.

What About Component Frameworks?

React, Vue, and similar JavaScript frameworks handle templating, composition, and most features listed above. They can run on a server or during a build step to produce HTML. They also offer compelling extras — scoped or encapsulated styles require coordination between HTML and CSS.

They were left off the feature comparison because their focus is crafting the DOM: data retrieval, state management, and interactivity. They're not really oriented toward being pure HTML processors. If you're using a JavaScript framework, you likely don't need a separate HTML preprocessor — though mixing Markdown into JSX or using Pug with Vue templates is entirely possible. Native web components were also excluded in favor of these richer frameworks.

Additional Considerations

  • Speed — How fast does it process, and does that matter for your use case?
  • Language — What is it written in, and does that fit the machines you support?
  • Server or build — Does it require a running web server, work as a one-time build step, or both?