Methods to write HTML in Swift?


Introducing SwiftHtml

This time we’ll begin all the things from scratch. Within the first part of this text I will present you learn how to setup the SwiftHtml as a bundle dependency and learn how to generate HTML output primarily based on a template file. Let’s begin by making a model new executable Swift bundle.

mkdir Instance
cd "$_"
swift bundle init --type=executable
open Bundle.swift

You can too begin with a macOS Command Line Device from Xcode if you want, however these days I want Swift Packages. Anyway, we should always add SwiftHtml as a dependency to our bundle straight away.


import PackageDescription

let bundle = Bundle(
    identify: "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(identify: "ExampleTests", dependencies: ["Example"]),
    ]
)

All proper, now we’re prepared to write down some Swift DSL code. We’ll begin with a very primary instance to get to know with SwiftHtml. In the principle.swift file we should always create a brand new HTML doc, then we are able to use SwiftHtml’s built-in renderer to print the html supply. 🖨

import SwiftHtml

let doc = Doc(.html) {
    Html {
        Head {
            Title("Good day, World!")
            
            Meta().charset("utf-8")
            Meta().identify(.viewport).content material("width=device-width, initial-scale=1")
        }
        Physique {
            Principal {
                Div {
                    H1("Good day, 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 may 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 attainable, so should you’ve written HTML earlier than this syntax ought to be very acquainted, besides that you do not have to write down opening and shutting tags, however we are able to 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 all the things at build-time, this fashion it is 100% certain that our HTML code will not have syntax points. After all you may nonetheless make semantic errors, however that is additionally attainable should you’re not utilizing a DSL. 😅

The primary 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 consequence builders to assemble the HTML node tree. SwiftHtml makes use of tags and it will construct a tree from them, this fashion it’s attainable to effectively render the complete construction with correct indentation or minification whether it is wanted.

The DocumentRenderer object can render a doc, it is usually attainable to create all kinds of SGML-based doc varieties, as a result of the SwiftHtml bundle comes with an abstraction layer. In case you check out the bundle construction it is 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 prime of the bottom parts. 🤔 For instance, should you check out the SwiftRss bundle you will note 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 characterize a customized merchandise in 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, identify and attributes. The Tag class is all about constructing a hierarchy in between the nodes. The Doc struct is a particular object which is answerable for rendering the doctype declaration earlier than the foundation tag if wanted, additionally after all the doc incorporates the foundation tag, which is the start of all the things. 😅

SwiftSgml additionally incorporates the DocumentRenderer and a easy TagBuilder enum, which is a consequence builder and it permits us to outline our construction in a SwiftUI-like model.

So the SwiftHtml bundle 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 may generate static web sites by utilizing the SwiftHtml library.

import Basis
import SwiftHtml

let doc = Doc(.html) {
    Html {
        Head {
            Title("Good day, World!")
            
            Meta().charset("utf-8")
            Meta().identify(.viewport).content material("width=device-width, initial-scale=1")
        }
        Physique {
            Principal {
                Div {
                    H1("Good day, 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)
    attempt html.write(to: file, atomically: true, encoding: .utf8)
}
catch {
    fatalError(error.localizedDescription)
}

This is only one means to make use of SwiftHtml, in my view static website turbines are superb, however the true enjoyable begins when you may render web sites primarily based on some form of dynamic knowledge. 🙃

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 out there Swift HTML DSL libraries that I used to be capable of finding on GitHub, however I used to be not fully glad with the at present out there options. Lots 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 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 challenge first.


import PackageDescription

let bundle = Bundle(
    identify: "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(identify: "ExampleTests", dependencies: ["Example"]),
    ]
)

We’ll want a brand new protocol, which we are able to use assemble a Tag, that is going to characterize 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 are able to 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 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 are able to 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 must 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 move round contextual variables for every template file. I am fairly proud of this strategy up to now. ☺️

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().identify(.viewport).content material("width=device-width, initial-scale=1")
            }
            Physique {
                Principal {
                    Div {
                        H1(context.title)
                        P(context.message)
                    }
                }
                .class("container")
            }
        }
    }
}

Lastly we simply have to write down some boilerplate code to begin up our Vapor internet server, we are able to 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 = attempt Surroundings.detect()
attempt LoggingSystem.bootstrap(from: &env)
let app = Utility(env)
defer { app.shutdown() }

app.get { req -> Response in
    let template = IndexTemplate(.init(title: "Good day, World!",
                                    message: "This web page was generated by the SwiftHtml library."))
    
    return req.templates.renderHtml(template)
}

attempt app.run()

Kind of that is it, it is best to be capable of run the server and hopefully it is best to see the rendered HTML doc should you open the http://localhost:8080/ handle utilizing your browser.

It is usually attainable to make use of one template inside one other, since you may name the render technique on a template and that template will return a Tag. The great thing about this strategy is that you would be able to compose smaller templates collectively, this fashion you may provide you with a pleasant challenge construction with reusable HTML templates written fully in Swift. I am more than pleased with this straightforward answer and looks like, for me, there isn’t a turning again to Leaf or Tau… 🤓

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox