/HTMLKit

A lightning fast, type-safe HTML templating library built for Swift

Primary LanguageSwiftMIT LicenseMIT

HTMLKit logo

HTMLKit

Render dynamic HTML templates in a typesafe and performant way! By using Swift's powerful language features and a pre-rendering algorithm, HTMLKit will render insanely fast templates but also catch bugs that otherwise might occur with other templating options.

Getting Started

Add the following in your Package.swift file

.package(url: "https://github.com/vapor-community/HTMLKit.git", from: "2.0.0-beta.2"),

You can use the following providers in order to use HTMLKit with Vapor 3 and for Vapor 4

Usage

To create a HTML template, conform to the HTMLTemplate protocol.

struct TemplateData {
    let name: String
    let handle: String
    let title: String?
}

struct SimpleTemplate: HTMLTemplate {

    @TemplateValue(TemplateData.self)
    var context

    var body: HTML {
        Document(type: .html5) {
            Head {
                Title { context.title }
                Author { context.name }
                    .twitter(handle: context.handle)
            }
            Body {
                Unwrap(context.title) { title in
                    P { title }
                }
            }
        }
    }
}

...
try SimpleTemplate().render(with: "Some string", for: req)

This will render somehing like this

<!DOCTYPE html>
<html>
    <head>
        <title>Some Title</title>
        <meta property='og:title' content='Some Title'>
        <meta name='twitter:title' content='Some Title'>
        <meta name='author' content='Mats'>
        <meta name='twitter:creator' content='@SomeTwitterHandle'>
    </head>
    <body>
        <p>Some Title</p>
    </body>
</html>

And to create a HTML component, just comform to the HTMLComponent protocol.

public struct Alert: HTMLComponent {

    let isDisimissable: Conditionable // This is a protocol that makes it possible to optimize if's
    let message: HTML

    public var body: HTML {
        Div {
            message
            
            IF(isDisimissable) {
                Button {
                    Span { "&times;" }
                        .aria(for: "hidden", value: true)
                }
                .type(.button)
                .class("close")
                .data("dismiss", value: "alert")
                .aria("label", value: "Close")
            }
        }
        .class("alert alert-danger bg-danger")
        .modify(if: isDisimissable) {
            $0.class("fade show")
        }
        .role("alert")
    }
}

This can then be used in another template or a static html page like:

struct SomePage: HTMLPage {

    var body: HTML {
        Div {
            Alert(isDismissable: false) {
                H3 { "Some Title" }
            }
        }
    }
}

Mixing HTMLKit with Leaf

You can easily mix Leaf and HTMLKit in the same project.

struct RenderingConfig: Content {
    let message: String
    let useHTMLKit: Bool
}

func renderLogin(on req: Request) -> Future<View> {
    let query = try req.query.decode(RenderingConfig.self)
    if config.useHTMLKit {
        return LoginPage().render(with: config, on: req)
    } else {
        return req.view().render("login-page", with: config)
    }
}

Localization

Much like SwiftUI, you can localize text by passing the localization key as a parameter.

...
var body: HTML {
    P("hello.world")
        .class("text-white")
}
...

This requires the Lingo framework, so you will also need to register the Lingo struct on the renderer.

var renderer = HTMLRenderer()
try renderer.registerLocalization(atPath: "workDir", defaultLocale: "en")

And if the locale changes based on some user input, then you can change the used locale in the template. This also effects how dates are presentet to the user.

struct LocalizedDateView: HTMLTemplate {

    struct Context {
        let date: Date
        let locale: String
    }

    var body: HTML {
        Div {
            P {
                context.date
                    .style(date: .short, time: .short)
            }
            P {
                context.date
                    .formatted(string: "MM/dd/yyyy")
            }
        }
        .enviroment(locale: context.locale)
    }
}

Useful Resources to Get Started

Known Issues

  • Linux compiler can sometimes struggle with compiling the code when a combination of complex use of generics, default values or deeply nested function builders are used.