Using Elementary for Vapor Templates
For Vapor templates, the framework default includes a template engine called Leaf. It is an officially supported template engine that can also be selected when creating a project. This time, I will try using Elementary as the template engine.
The Elementary repository is below.
There is also a separate repository for the package used with Vapor.
I think server-side Swift template engines can be broadly divided into two types. One is the traditional variable-embedding type. Mustache is probably a representative example.
Leaf follows that lineage as well. Although its writing style is influenced by the Swift language, it uses HTML files and inserts template tags into them for replacement.
The other lineage follows the SwiftUI style. Basically it is Swift code, and part of it defines the tag structure in a DSL-like way. This is a familiar method for people who build iOS or Mac apps. It is also called declarative UI.
There are already many libraries of this type, but Elementary is a later library. The README explains why it was created even though many others already exist.
In "Motivation and other packages," it says the following.
> Plot, HTMLKit, and Swim are all excellent packages for doing a similar thing. > > My main motivation for Elementary was to create an experience like these (Swift Forums post for more context), but > > - stay true to HTML tag names and conventions (including the choice of lowercase types) > - avoid allocating an intermedate structure and go straight to streaming HTML > - using generics to stay away from allocating a ton of lists of existential anys > - have a list of attributes go before the content block > - provide attribute fallthrough and merging > - zero dependencies on other packages
Plot, HTMLKit, and Swim are all template libraries of the latter type, but their release momentum has not been very strong recently. Each library also has its own dialect, so there will likely be preferences depending on the person. One motivation is to follow HTML tag names and conventions, which makes it easy for people who know HTML to start using it smoothly.
Elementary is great because it can be used with both Vapor and Hummingbird. I will try introducing it right away in Vapor, which I use regularly.
This time I will create a simple project that only displays something like Hello World.
Adding the Library
First, add the dependency information to Package.swift. In addition to adding entries to dependencies in two places, you need to change the version number in platforms. It seems it does not support versions before v14. The rewritten Package.swift looks like this.
Package.swift
...
let package = Package(
...
platforms: [
// Change to v14
.macOS(.v14)
],
dependencies: [
...
// Add the elementary dependency
.package(url: "https://github.com/vapor-community/vapor-elementary.git", from: "0.1.0")
],
targets: [
.executableTarget(
...
dependencies: [
...
// Add the elementary dependency
.product(name: "VaporElementary", package: "vapor-elementary")
],
...
),
...
],
...
)
...
Writing the Template
Next, write the template in /Sources/App/routes.swift and rewrite the routing information.
To render the template, you need to use HTMLResponse. This is included in the VaporElementary package, so write the import declaration.
This time I write the template named ElementaryPage directly below the route definition, but because this is just Swift code, you can move it to another file or change it freely in many ways.
The page template needs to implement the HTMLDocument protocol. Specifically, it needs title, head, and body. The feeling of writing tags in a hierarchy is quite close to SwiftUI, so for someone using Swift, this feels more natural. HTML attributes have a somewhat unique writing style, so as an example I specify id and class on the h1 element.
You can write a template like this.
import Elementary
import Vapor
import VaporElementary
func routes(_ app: Application) throws {
app.get { _ async in
HTMLResponse {
ElementaryPage()
}
}
}
struct ElementaryPage: HTMLDocument {
var title = "Elementary"
var head: some HTML {
meta(.charset(.utf8))
}
var body: some HTML {
main {
h1(.id("my-h1-id"), .class("my-h1-class")) {
"Hello Elementary!"
}
}
}
}
Templates can also nest fragments as components. I plan to write about components in later posts.