This tutorial is all about rendering HTML docs utilizing a model new DSL library known as SwiftHtml and the Vapor net framework.
Vapor
Introducing SwiftHtml
This time we’ll begin all the pieces from scratch. Within the first part of this text I’ll present you learn how to setup the SwiftHtml as a bundle dependency and learn how to generate HTML output based mostly on a template file. Let’s begin by making a model new executable Swift bundle.
mkdir Instance
cd "$_"
swift bundle init --type=executable
open Package deal.swift
You too can begin with a macOS Command Line Device from Xcode if you want, however these days I desire Swift Packages. Anyway, we must always add SwiftHtml as a dependency to our bundle instantly.
import PackageDescription
let bundle = Package deal(
title: "Instance",
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/binarybirds/swift-html", from: "1.2.0"),
],
targets: [
.executableTarget(name: "Example", dependencies: [
.product(name: "SwiftHtml", package: "swift-html"),
]),
.testTarget(title: "ExampleTests", dependencies: ["Example"]),
]
)
All proper, now we’re prepared to put in writing some Swift DSL code. We will begin with a extremely primary instance to get to know with SwiftHtml. Within the most important.swift
file we must always create a brand new HTML doc, then we will use SwiftHtml’s built-in renderer to print the html supply. šØ
import SwiftHtml
let doc = Doc(.html) {
Html
Head
Title("Hiya, World!")
Meta().charset("utf-8")
Meta().title(.viewport).content material("width=device-width, initial-scale=1")
Physique
Foremost
Div
H1("Hiya, World!")
P("This web page was generated by the SwiftHtml library.")
.class("container")
}
let html = DocumentRenderer(minify: false, indent: 2).render(doc)
print(html)
As you’ll be able to see the code is fairly easy, particularly if you already know a bit about HTML. The SwiftHtml library tries to observe the naming conventions as carefully as doable, so for those who’ve written HTML earlier than this syntax ought to be very acquainted, besides that you do not have to put in writing opening and shutting tags, however we will make the most of the Swift compiler to do the boring repetative duties as an alternative of us.
Since we’re utilizing a website particular language in Swift, the compiler can type-check all the pieces at build-time, this manner it is 100% certain that our HTML code will not have syntax points. After all you’ll be able to nonetheless make semantic errors, however that is additionally doable for those who’re not utilizing a DSL. š
The primary benefit right here is that you just will not be capable to mistype or misspell tags, and you do not even have to consider closing tags, however you should utilize outcome builders to assemble the HTML node tree. SwiftHtml makes use of tags and it will construct a tree from them, this manner it’s doable to effectively render your complete construction with correct indentation or minification whether it is wanted.
The DocumentRenderer
object can render a doc, it’s also doable to create all types of SGML-based doc sorts, as a result of the SwiftHtml bundle comes with an abstraction layer. For those who check out the bundle construction it’s best to see that contained in the Sources listing there are a number of different directories, the core of the bundle is the SwiftSgml part, which permits builders to create different area particular languages on high of the bottom elements. š¤
For instance, for those who check out the SwiftRss bundle you will notice that it is a easy extension over the SwiftSgml library. You possibly can subclass the Tag
object to create a brand new (area particular) tag with an underlying Node
object to signify a customized merchandise on your doc.
The SwiftSgml library could be very light-weight. The Node
struct is a illustration of a given SGML node with a customized kind, title and attributes. The Tag
class is all about constructing a hierarchy in between the nodes. The Doc
struct is a particular object which is liable for rendering the doctype declaration earlier than the foundation tag if wanted, additionally in fact the doc comprises the foundation tag, which is the start of all the pieces. š
SwiftSgml additionally comprises the DocumentRenderer
and a easy TagBuilder
enum, which is a outcome builder and it permits us to outline our construction in a SwiftUI-like fashion.
So the SwiftHtml bundle is only a set of HTML guidelines on high of the SwiftSgml library and it follows the W3C HTML reference guides. You should utilize the output string to avoid wasting a HTML file, this manner you’ll be able to generate static web sites by utilizing the SwiftHtml library.
import Basis
import SwiftHtml
let doc = Doc(.html) {
Html
Head
Title("Hiya, World!")
Meta().charset("utf-8")
Meta().title(.viewport).content material("width=device-width, initial-scale=1")
Physique
Foremost
Div
H1("Hiya, World!")
P("This web page was generated by the SwiftHtml library.")
.class("container")
}
do
let dir = FileManager.default.homeDirectoryForCurrentUser
let file = dir.appendingPathComponent("index.html")
let html = DocumentRenderer(minify: false, indent: 2).render(doc)
strive html.write(to: file, atomically: true, encoding: .utf8)
catch
fatalError(error.localizedDescription)
This is only one approach to make use of SwiftHtml, in my view static web site turbines are high-quality, however the actual enjoyable begins when you’ll be able to render web sites based mostly on some type of dynamic knowledge. š
Utilizing SwiftHtml with Vapor
Vapor has an official template engine known as Leaf plus the group additionally created a type-safe HTML DSL library known as HTMLKit, so why create one thing very related?
Properly, I attempted all of the accessible Swift HTML DSL libraries that I used to be capable of finding on GitHub, however I used to be not totally glad with the at present accessible options. A lot of them was outdated, incomplete or I merely did not like the flavour of the DSL. I wished to have a library which is freakin’ light-weight and follows the requirements, that is the explanation why I’ve constructed SwiftHtml. š¤
How can we combine SwiftHtml with Vapor? Properly, it is fairly easy, let’s add Vapor as a dependency to our mission first.
import PackageDescription
let bundle = Package deal(
title: "Instance",
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/binarybirds/swift-html", from: "1.2.0"),
.package(url: "https://github.com/vapor/vapor", from: "4.54.0"),
],
targets: [
.executableTarget(name: "Example", dependencies: [
.product(name: "SwiftHtml", package: "swift-html"),
.product(name: "Vapor", package: "vapor"),
]),
.testTarget(title: "ExampleTests", dependencies: ["Example"]),
]
)
We will want a brand new protocol, which we will use assemble a Tag
, that is going to signify a template file, so let’s name it TemplateRepresentable
.
import Vapor
import SwiftSgml
public protocol TemplateRepresentable
@TagBuilder
func render(_ req: Request) -> Tag
Subsequent, we’d like one thing that may render a template file and return with a Response object, that we will use inside a request handler once we setup the route handlers in Vapor. Since we’ll return a HTML string, it’s essential to set the right response headers too.
import Vapor
import SwiftHtml
public struct TemplateRenderer
var req: Request
init(_ req: Request)
self.req = req
public func renderHtml(_ template: TemplateRepresentable, minify: Bool = false, indent: Int = 4) -> Response
let doc = Doc(.html) template.render(req)
let physique = DocumentRenderer(minify: minify, indent: indent).render(doc)
return Response(standing: .okay, headers: ["content-type": "text/html"], physique: .init(string: physique))
Lastly we will lengthen the built-in Request object to return a brand new template renderer if we’d like it.
import Vapor
public extension Request
var templates: TemplateRenderer .init(self)
Now we simply need to create a HTML template file. I am normally making a context object proper subsequent to the template this manner I am going to have the ability to cross round contextual variables for every template file. I am fairly pleased with this strategy thus far. āŗļø
import Vapor
import SwiftHtml
struct IndexContext
let title: String
let message: String
struct IndexTemplate: TemplateRepresentable {
let context: IndexContext
init(_ context: IndexContext)
self.context = context
func render(_ req: Request) -> Tag {
Html
Head
Title(context.title)
Meta().charset("utf-8")
Meta().title(.viewport).content material("width=device-width, initial-scale=1")
Physique
Foremost
Div
H1(context.title)
P(context.message)
.class("container")
}
}
Lastly we simply have to put in writing some boilerplate code to start out up our Vapor net server, we will use the app occasion and set a get request handler and render our template utilizing the newly created template renderer extension on the Request object.
import Vapor
import SwiftHtml
var env = strive Surroundings.detect()
strive LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer app.shutdown()
app.get req -> Response in
let template = IndexTemplate(.init(title: "Hiya, World!",
message: "This web page was generated by the SwiftHtml library."))
return req.templates.renderHtml(template)
strive app.run()
Roughly that is it, it’s best to be capable to run the server and hopefully it’s best to see the rendered HTML doc for those who open the localhost:8080 deal with utilizing your browser.
It’s also doable to make use of one template inside one other, since you’ll be able to name the render methodology on a template and that template will return a Tag. The great thing about this strategy is which you can compose smaller templates collectively, this manner you’ll be able to provide you with a pleasant mission construction with reusable HTML templates written totally in Swift. I am very happy with this straightforward answer and looks like, for me, there isn’t a turning again to Leaf or Tau… š¤