Templating with Mustache: an Interactive Tutorial
When writing your own tools, generating code is a very frequent need. For example, in the context of iOS development, SwiftGen is a tool that creates code from .xcassets files, storyboards, and Localizable.strings, allowing developers to rely on type safe code instead of string-based APIs.
Usually these needs are grouped into patterns, technically called templates: they contain static text and dynamic content (variables, objects and lists) that can be accessed from within the template. Specific programs are able to process these templates: templating engines. They take an input, composed of a template and the dynamic content (often called context), and generate an output.
In the Swift ecosystem, there are a few popular templating engines, with slightly different specifications: Stencil, Leaf (from Vapor), and SwiftMustache. The last one is a Swift implementation for the Mustache standard, written by Adam Fowler and is part of the Hummingbird project:
There wasn’t a full implementation of Mustache written in Swift that compiled on Linux. Mustache is a web standard and we should have an implementation of it in our Swift ecosystem.
Adam Fowler
In this interactive post, you’ll learn how to harness the power of Mustache in your Swift programs, with hands-on live examples that let you experiment and learn by doing. Let’s get started! 🚀
Adding SwiftMustache via SPM
To use Mustache from your CLI tool or your server, start by adding first the package dependency to your Package.swift file:
...
dependencies: [
.package(
url: "https://github.com/hummingbird-project/swift-mustache.git",
from: "2.0.0"
),
]
...
After that, add the target dependency to your target:
...
targets: [
.executableTarget(
name: "my-great-tool",
dependencies: [
.product(name: "Mustache", package: "swift-mustache"),
]
)
]
...
The Basics
Now you’re ready to import Mustache and start using it. The simplest way to use it, is initializing a MustacheTemplate
from a string:
let template = try MustacheTemplate(string: "Hello, {{ firstName }}!")
You can notice the double curly brackets wrapping a variable, called firstName
. When rendering this template, Mustache will look for this key in the object you pass in as the input. For the sake of this example, let’s create a simple JSON dictionary, and see how Mustache renders it.
Click the Render button to see the result. Then try adding a lastName
key to the JSON dictionary, and also update the template to check how it changes the output.
Using Structs (Or Other Types) as Input
In the previous example, the context was a dictionary. But due to how SwiftMustache was implemented with reflection and Mirror
, you can also use other types, like structs.
struct Person {
let firstName: String
let lastName: String
}
let person = Person(firstName: "John", lastName: "Appleseed")
let template = try MustacheTemplate(
string: "Hello, {{ firstName }} {{ lastName }}!"
)
let output = template.render(person)
// Renders: "Hello, John Appleseed!"
This is very useful when you want to generate code for your own types, guaranteeing that the input data is always consistent with your own types.
Sections
Mustache defines itself as a logic-less templating engine, but it actually supports conditionals and iterating through arrays. This is because, differently from other templating engines, Mustache doesn’t use if/else statements, nor loops. Instead, it uses sections. According to the definition in the Mustache manual:
Sections render blocks of text zero or more times, depending on the value of the key in the current context.
For example, you can use a section to check if a key is present in the context, or if it’s true. Alternatively, it also can be used to iterate through an array.
Conditional Values
To open a section, you use the {{#key}}
syntax, and {{/key}}
to close it:
Click to render the template. Then, try changing the isAllowed
key to false
, or just delete it, to see how the output changes. If you look closely, you’ll notice this happens because of the {{^isAllowed}}
inverted section: it becomes active when the key is not present or is false.
Similarly, you can check the presence of a key, and render within a section only if it’s present:
In the example above, the superPower
key is not present in the context, so the section is not rendered. Try adding it to the context, something like "Crazy dribbling"
, and see the result.
Notice how, inside the optional superPower
section, the value is accessed with {{ . }}
syntax. This is useful when accessing the current value within a section, as you’ll see in the next paragraph about arrays.
Iterating Through Arrays
With Mustache, you can iterate through arrays without having to write a loop. The great thing about it, is that you use the same section syntax, but instead of the value for the key being a boolean or a string, it’s an array.
In the example above, fruits
is an array, and the section is rendered once for each element in the array. Because each element of the array is a string, and not another object, the {{ . }}
syntax is used to access the current element.
Alternatively, you can also access nested keys of the current element, by passing the key inside the curly brackets:
Try adding more products to the array, and see how the output changes, or add new keys to the products, like description
, and update the template accordingly to reflect the new structure.
Functions
Another great capability of Mustache is the ability to use functions in the template - they run at the time of rendering. For example, you can render the time and date, or pick a random element from an array. While in other implementations of Mustache you can use a regular function or closure, in SwiftMustache you do so by using a MustacheLambda
, and initializing it with a block:
You can click the Render button multiple times, to see how the output changes, indicating that the functions are executed at the time of rendering.
But there’s also another interesting way of using functions. Mustache allows to use a function itself as a section. For example, imagine you want to wrap a string in a <strong>
tag:
Template Libraries
Initializing a MustacheTemplate
from a string is great for simple cases, but as you write longer and more complex templates, you’ll want to have them separated into different files. This is where template libraries come in: they group different templates together, identifying each by name.
There are two ways to create a library. The first is manually, where you register each template by calling the register(_:named:)
method. The other option, more convenient, is add a folder of templates as a resource in your target and use it to initialize a library:
#1
func loadLibrary() async throws -> MustacheLibrary {
#2
guard let path = Bundle.module.resourcePath else {
throw Error.resourcesBundleNotFound
}
#3
let templatesPath = path.appending("/Templates")
#4
return try await MustacheLibrary(directory: templatesPath)
}
The code above can be used from an executable SPM target - a CLI tool or a server - and it does the following:
- Declare a function that returns a
MustacheLibrary
- it can throw an error, and also usesasync
to make it non-blocking - Look for the resources path provided by the Swift Package Manager, and throw an error if it’s not found
- Assuming you added all your templates to a Templates folder, append this path to the resources path
- Finally, initialize the
MustacheLibrary
with the templates directory
From there, you can use the MustacheLibrary
to either get a template by its file name, or to directly render it:
let library = try await loadLibrary()
let context = ["email": "john@example.com"]
// Get a template by its file name
// It's an optional because the file might not exist.
if let template = try library.template(named: "welcome-email") {
let output = try template.render(context)
}
// Or render directly from the library
let output = try library.render(template: "welcome-email", context: context)
Tags
Mustache supports different tags, which are used to add more functionality to the templates. This section will cover three of the most important ones.
Escaping HTML
As one of the most common use cases for templating is generating HTML, Mustache automatically escapes variables in a way that’s compatible with HTML.
For example, if you wanted to render the title of a website, and you use characters such as &
, <
, >
or "
, they will be properly escaped: Tim & Craig
becomes Tim & Craig
.
In case you want to render raw HTML, you can use three curly brackets, instead of two: {{{ rawHTML }}}
. You can play with the example below to see how it works. Add another curly brackets for the description
key, making it {{{ description }}}
, and see how they will differ once rendered.
Comments
Just as regular code, you can also add comments to templates, by using the {{! comment }}
syntax:
Welcome to !
Partials
Sometimes, when writing long templates, you’ll want to break them apart into smaller pieces. This has two main advantages. First, it allows you to split the templates into smaller bits, making it easier to read and understand. Second, it allows you to reuse the same template in multiple places: this is where partials come in.
In the template above, there are two partials: header
and item
. When Mustache renders the template, it will first look for the a template named header
in the library. After that, if items
is not empty, it will look for another template, this time item
, and render it using each item as the context.
Explore Further
Using templates is a very powerful capability you can implement in your own tools, and Mustache is an elegant way to do it. Be it for code generation, writing HTML, or any other need, it’s a great skill to have!
Special thanks to Adam Fowler for his work on SwiftMustache, and for reviewing this post.
Here are some additional resources you can use to learn more about Mustache:
- Transforms: Specific to SwiftMustache, transforms allow you to perform modifications to a value, right from the template. Some examples are: capitalize a string, sort an array before rendering it, count the items in a sequence, and more.
- Mustache Manual
- Swift Mustache Features
- Swift Mustache Syntax
If you have any questions, comments, or feedback on this tutorial, drop us a line at X or Mastodon.
See you at the next post. Have a good one!