Let's be friends

Introduction

Templating systems transform static HTML into dynamic, data-driven pages by combining content and markup. Over time, they’ve evolved into a range of styles, from minimal syntax enhancements to tightly integrated frameworks.

Here’s a breakdown of major approaches, highlighting how different philosophies address the same goal: creating flexible, maintainable web pages.

Some regular markup

index.html
<h1>People</h1>

<ul>
	<li>Peter</li>
	<li>Jewell</li>
	<li>Stephanie</li>
	<li>Andy</li>
</ul>

This is fine to just type. But what if you don’t know the names? And what if there are 872 of them?

Philosophy: Write static content directly in HTML when simplicity and no logic are sufficient.

Dual languages

index.php
<?php
	$name = "Ivy";
?>

<h1><?=$name?></h1>
variations.php
<?php
	$people = ["Peter", "Jewell", "Stephanie", "Andy"];
?>

<?php
	foreach ($people as $person) {
		echo "<li>";
			echo $person;
		echo "</li>";
	}
	// you can write this many ways...
?>

<?php
	foreach ($people as $person) {
		echo "<li>$person</li>"; // interpolation with double quotes
	}
?>

<?php foreach ($people as $person) { ?>
	<li><?= $person ?></li>
<?php } /* clear seperation + short-hand echo statement */ ?>

<?php foreach ($people as $person): ?>
	<li><?=$person?></li>
<?php endforeach; /* another optional syntax */ ?>

<?php
	foreach ($people as $person):
		include('person-card.php');
	endforeach;

	foreach ($people as $person) {
		renderPerson($person);
	}
	// we try and align it with how JavaScript looks, so it's an easy transition
?>
index.js
<% var name = "Ivy"; %>

<h1><%= name %></h1>
people.js
<% var people = ["Peter", "Jewell", "Stephanie", "Andy"]; %>

<ul>
	<% people.forEach( function(person) { %>
		<li><%= person %></li>
	<% }); %>
</ul>

<ul>
	<% people.forEach( (person)=> { %>
		<li><%= person %></li>
	<% }); %>
</ul>

PHP and its stack are often taken for granted. Setting up something comparable in JavaScript, like a Node project with NPM, Express, EJS, and a server, feels like a LOT of setup just to get basic functionality on the screen. In contrast, PHP benefits from the LAMP stack—it’s already configured for us, so we don’t think about it.

To be fair: let’s take a moment to acknowledge what’s happening behind the scenes: a built-in runtime, a web server, database integration, and a simple directory structure—all ready to go. This preconfigured environment makes PHP an incredibly accessible entry point into templated web development. It’s not just the tooling; it’s also the syntax. PHP blends seamlessly with HTML, allowing you to incrementally transition from static content to dynamic features.

In terms of philosophy, PHP represents the first step from static HTML toward dynamic templating and more complex systems.

PHP operates with a dual-language system: by default, everything is treated as HTML (essentially, just a string). Anything enclosed within the PHP delimiters (<?php?>) is interpreted and executed as PHP.

(note: yes, there should be a <ul>, but let’s keep things simple to focus on the parts that matter most for this conversation 😉

People have issues with PHP. Ours was always that it can be written really ugly / and the type of people who consider themselves “PHP developers” are usually the people who have found the ugliest ways to write it (in our experience). BUT! You can choose to write it in a way that’s pretty dang readable. Which style do you like best? It’s also about how you choose to compose the pieces. If you’re doing something more programmatic, it might make sense to stay in PHP land. If you’re building templates for everyone on your team to work on, it will make sense to stay more aligned with HTML. You could think about it like HTML in PHP – or PHP in HTML.

Having all of these options – isn’t necessarily a great thing!

  • PHP (1995)
    PHP blends HTML with server-side logic using <?php ?> delimiters.
  • ASP Classic (1996)
    Similar to PHP, but Microsoft-centric, using <% %> syntax for embedding logic.
  • EJS (JavaScript/Node.js, 2010)
    A direct parallel to PHP, embedding JavaScript logic within HTML using <% %>.

EJS feels like the JavaScript world’s response to PHP, designed for teams already working in JavaScript. It allows them to use a familiar language for both application logic and template rendering without introducing a new programming language like PHP.

To use EJS, you need to set up a Node.js environment, which involves installing Node.js, creating a project with npm init, and installing EJS along with Express (since it’s usually paired with Express for routing). Then, you configure Express to use EJS as the templating engine, set up your views folder for .ejs templates, and write server-side code to render those templates. Compared to PHP, which runs out of the box on most servers, EJS requires more upfront setup—installing dependencies, configuring your server, and writing boilerplate code—but it’s tightly integrated with JavaScript and modern frameworks, making it a solid choice for Node-based projects.

Philosophy: Embed server-side logic into otherwise static HTML, bridging the gap between static content and dynamic web applications.

HTML-centric templating systems

component.erb
<h1><%= title %></h1>

<ul>
  <% people.each do |person| %>
    <li><%= person %></li>
  <% end %>
</ul>
handlebars.hbs
<h1>{{title}}</h1>

<ul>
  {{#each people as |person|}}
    <li>{{person}}</li>
  {{/each}}
</ul>
liquid.liquid
<h1>{{ title }}</h1>

<ul>
  {% for person in people %}
    <li>{{ person }}</li>
  {% endfor %}
</ul>
blade.php
<h1>{{ $title }}</h1>

<ul>
  @foreach ($people as $person)
    <li>{{ $person }}</li>
  @endforeach
</ul>

Blade offers a more “magic-like” integration by seamlessly combining PHP logic with HTML.

component.svelte
<h1>{title}</h1>

<ul>
  {#each people as person}
    <li>{person}</li>
  {/each}
</ul>

HTML-centric templating systems prioritize readability and a clean separation between logic and markup. They are especially useful when collaborating with designers or front-end developers who may not need to write complex logic. Despite their minor differences, once you understand one, picking up another is straightforward.

Here are some in this category.

handlebars.hbs, mustache.mustache, liquid.liquid, blade.php, nunjucks.njk, twig.twig, dot.dot, jinja2.j2, tera.tera, volt.volt

  • JSP (Java Server Pages) (1999)
    Java’s templating engine using <% %> and custom tags for logic.
  • ERB (1999)
    Ruby’s default templating system uses <%= %> for output and <% %> for logic.
  • Mustache (2009)
    Minimal templating language using {{ }} for placeholders.
  • Handlebars (2010)
    An extension of Mustache with additional helpers and logic.
  • Liquid (2006)
    Created by Shopify, using {{ }} and {% %} for dynamic content and logic.
  • Twig (2011)
    A modern PHP-based templating engine with {% %} for logic and {{ }} for output.
  • Blade (2011)
    Laravel’s templating engine with a “magic” integration of PHP and @ directives.
  • Svelte (2016)
    HTML-centric templating system with reactive declarations like {#each} and <script> tags.

Philosophy: Prioritize readability and a clear separation of logic and markup, making it easier to collaborate with non-developers.

Minimal whitespace-Sensitive Templating Systems

index.haml
%h1 People

%ul
  - people = ["Peter", "Jewell", "Stephanie", "Andy"]
  - people.each do |person|
    %li= person
index.pug
h1 People

ul
  each person in ["Peter", "Jewell", "Stephanie", "Andy"]
    li= person

Whitespace-sensitive templating systems eliminate most of the boilerplate found in traditional templating languages by using indentation to define structure. These systems prioritize brevity, readability, and a clean syntax, making them ideal for developers who value minimalism or work extensively in the Ruby or JavaScript ecosystems.

However, these systems can have a steeper learning curve for beginners due to their strict reliance on indentation for structure. Errors like misaligned whitespace can break templates, requiring careful attention to detail.

  • HAML – 2006
    The first widely adopted whitespace-sensitive templating system, focusing on cleaner syntax in the Ruby ecosystem.
  • Slim – 2009
    A minimalist evolution of HAML, also in the Ruby world, with an even more streamlined syntax.
  • Pug (formerly Jade) – 2010
    Introduced for Node.js, adopting whitespace-sensitive syntax for JavaScript projects, emphasizing simplicity and brevity.

Whitespace-sensitive systems balance between developer ergonomics and strict formatting. By removing unnecessary syntax, these systems promote focus on content while enforcing structure through indentation. They’re particularly appealing for teams working in Ruby or Node.js, where these templating languages align with the broader ecosystem philosophy.

Philosophy: Enforce clean and concise syntax by using indentation to define structure, promoting minimalism and readability.

Native JavaScript Template Strings

index.js
const people = ["Peter", "Jewell", "Stephanie", "Andy"];

const listHTML = `
  <ul>
    ${people.map(person => `<li>${person}</li>`).join('')}
  </ul>
`;

document.body.innerHTML = listHTML;

Introduced in ES6 (2015), template literals are a way to embed variables, expressions, and even logic directly into strings. They provide a lightweight, no-library-needed alternative to traditional templating systems when working with JavaScript.

Template strings keep everything in JavaScript, which can be useful for small-scale needs or when working in environments where introducing additional dependencies isn’t ideal. However, they can get messy for larger, more complex templates and lack the structure and organization provided by dedicated templating systems.

Philosophy: Keep everything in JavaScript, offering a lightweight, dependency-free solution for embedding logic into HTML-like strings.

Directive-based templating

index.html
<h1>{{title}}</h1>

<ul>
	<li *ngFor="let person of people">{{person}}</li>
</ul>
index.html / component.vue
<h1>{{title}}</h1>

<ul>
	<li v-for='person in people'>{{person}}</li>
</ul>

Directive-based templating systems use special attributes or markers within HTML to dynamically manipulate the DOM. These directives act as declarative instructions, telling the framework how to handle data binding, conditional rendering, event handling, and more. They are particularly powerful for creating interactive and reactive user interfaces while maintaining a clean and readable structure.

Philosophy: Use declarative attributes or markers in HTML to bind data, handle events, and render dynamic content in an intuitive way.

JavaScript-integrated templating

react.js
function PeopleList({ people }) {
  return (
    <ul>
      {people.map((person) => (
        <li key={person}>{person}</li>
      ))}
    </ul>
  );
}

JSX allows developers to write HTML-like markup within JavaScript files, providing a syntax that feels closer to declarative HTML but is actually syntactic sugar for JavaScript function calls. While this offers a consistent environment for React developers to work in, it also creates a non-standard system that deviates significantly from both native JavaScript and HTML. (First introduced with React in 2013)

Philosophy: Combine HTML-like syntax with JavaScript functions for seamless integration, but at the cost of straying from web standards.

Drama

Jan 3rd, 2025.

This response from Chat was too funny...

Let's be friends