[ad_1]
This tutorial is all about rendering HTML docs utilizing a model new DSL library known as SwiftHtml and the Vapor internet framework.
Vapor
Introducing SwiftHtml
This time we’ll begin every little thing from scratch. Within the first part of this text I will present you setup the SwiftHtml as a package deal dependency and generate HTML output based mostly on a template file. Let’s begin by making a model new executable Swift package deal.
mkdir Instance
cd "$_"
swift package deal init --type=executable
open Package deal.swift
It’s also possible to begin with a macOS Command Line Device from Xcode if you want, however these days I desire Swift Packages. Anyway, we should always add SwiftHtml as a dependency to our package deal immediately.
import PackageDescription
let package deal = 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 jot down some Swift DSL code. We’ll begin with a very fundamental instance to get to know with SwiftHtml. Within the primary.swift file we should 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("Whats up, World!")
Meta().charset("utf-8")
Meta().title(.viewport).content material("width=device-width, initial-scale=1")
}
Physique {
Foremost {
Div {
H1("Whats up, 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 possibly can see the code is fairly simple, particularly if you recognize a bit about HTML. The SwiftHtml library tries to observe the naming conventions as intently as potential, so if you happen to’ve written HTML earlier than this syntax must be very acquainted, besides that you do not have to jot down opening and shutting tags, however we will make the most of the Swift compiler to do the boring repetative duties as a substitute of us.
Since we’re utilizing a website particular language in Swift, the compiler can type-check every little thing at build-time, this fashion it is 100% positive that our HTML code will not have syntax points. In fact you possibly can nonetheless make semantic errors, however that is additionally potential if you happen to’re not utilizing a DSL. 😅
The principle benefit right here is that you just will not be capable of 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 fashion it’s potential to effectively render the whole construction with correct indentation or minification whether it is wanted.
The DocumentRenderer object can render a doc, it’s also potential to create all kinds of SGML-based doc varieties, as a result of the SwiftHtml package deal comes with an abstraction layer. In the event you check out the package deal construction it’s best to see that contained in the Sources listing there are a number of different directories, the core of the package deal is the SwiftSgml element, which permits builders to create different area particular languages on prime of the bottom parts. 🤔
For instance, if you happen to check out the SwiftRss package deal you will note that it is a easy extension over the SwiftSgml library. You’ll be able to 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 sort, 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 basis tag if wanted, additionally after all the doc incorporates the basis tag, which is the start of every little thing. 😅
SwiftSgml additionally incorporates the DocumentRenderer and a easy TagBuilder enum, which is a outcome builder and it permits us to outline our construction in a SwiftUI-like model.
So the SwiftHtml package deal is only a set of HTML guidelines on prime of the SwiftSgml library and it follows the W3C HTML reference guides. You should utilize the output string to save lots of a HTML file, this fashion you possibly can generate static web sites through the use of the SwiftHtml library.
import Basis
import SwiftHtml
let doc = Doc(.html) {
Html {
Head {
Title("Whats up, World!")
Meta().charset("utf-8")
Meta().title(.viewport).content material("width=device-width, initial-scale=1")
}
Physique {
Foremost {
Div {
H1("Whats up, 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 website turbines are superb, however the actual enjoyable begins when you possibly can render web sites based mostly on some type of dynamic information. 🙃
Utilizing SwiftHtml with Vapor
Vapor has an official template engine known as Leaf plus the neighborhood 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 happy 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 needed to have a library which is freakin’ light-weight and follows the requirements, that is the rationale 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 package deal = 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’ll 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 after we setup the route handlers in Vapor. Since we’ll return a HTML string, it’s essential to set the correct 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 should create a HTML template file. I am often making a context object proper subsequent to the template this fashion I am going to have the ability to cross round contextual variables for every template file. I am fairly proud of this method to date. ☺️
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 jot down some boilerplate code to start out up our Vapor internet 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 Setting.detect()
strive LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer { app.shutdown() }
app.get { req -> Response in
let template = IndexTemplate(.init(title: "Whats up, World!",
message: "This web page was generated by the SwiftHtml library."))
return req.templates.renderHtml(template)
}
strive app.run()
Kind of that is it, it’s best to be capable of run the server and hopefully it’s best to see the rendered HTML doc if you happen to open the localhost:8080 tackle utilizing your browser.
It is usually potential to make use of one template inside one other, since you possibly can name the render methodology on a template and that template will return a Tag. The fantastic thing about this method is which you could compose smaller templates collectively, this fashion you possibly can give you a pleasant mission construction with reusable HTML templates written totally in Swift. I am more than pleased with this straightforward answer and looks like, for me, there isn’t any turning again to Leaf or Tau… 🤓
[ad_2]
