Skip to content

An awesome Swift HTML DSL library using result builders.

License

Notifications You must be signed in to change notification settings

m-y-n-o-n-a/swift-html

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

93 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftHtml

An awesome Swift HTML DSL library using result builders.

import SwiftHtml 

let doc = Document(.html) {
    Html {
        Head {
            Title("Hello Swift HTML DSL")
            
            Meta().charset("utf-8")
            Meta().name(.viewport).content("width=device-width, initial-scale=1")

            Link(rel: .stylesheet).href("./css/style.css")
        }
        Body {
            Main {
                Div {
                    Section {
                        Img(src: "./images/swift.png", alt: "Swift Logo")
                            .title("Picture of the Swift Logo")
                        H1("Lorem ipsum")
                            .class("red")
                        P("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
                            .class(["green", "blue"])
                            .spellcheck(false)
                    }

                    A("Download SwiftHtml now!")
                        .href("https://github.com/binarybirds/swift-html/")
                        .target(.blank)
                        .download()
                        
                    Abbr("WTFPL")
                        .title("Do What The Fuck You Want To Public License")
                }
            }
            .class("container")

            Script().src("./js/main.js").async()
        }
    }
}

let html = DocumentRenderer(minify: false, indent: 2).render(doc)
print(html)

Install

You can simply use SwiftHtml as a dependency via the Swift Package Manager:

.package(url: "https://github.com/binarybirds/swift-html", from: "1.6.0"),

Add the SwiftHtml product from the swift-html package as a dependency to your target:

.product(name: "SwiftHtml", package: "swift-html"),

Import the framework:

import SwiftHtml

That's it.

Creating custom tags

You can define your own custom tags by subclassing the Tag or EmptyTag class.

You can follow the same pattern if you take a look at the core tags.

open class Div: Tag {

}

// <div></div> - standard tag

open class Br: EmptyTag {
    
}
// <br> - no closing tag

By default the name of the tag is automatically derived from the class name (lowercased), but you can also create your own tag type & name by overriding the createNode() class function.

open class LastBuildDate: Tag {

    open override class func createNode() -> Node {
        Node(type: .standard, name: "lastBuildDate")
    }
}

// <lastBuildDate></lastBuildDate> - standard tag with custom name

It is also possible to create tags with altered content or default attributes.

open class Description: Tag {
    
    public init(_ contents: String) {
        super.init()
        setContents("<![CDATA[" + contents + "]]>")
    }
}
// <description><![CDATA[lorem ipsum]]></description> - content wrapped in CDATA

open class Rss: Tag {
    
    public init(@TagBuilder _ builder: () -> [Tag]) {
        super.init(builder())
        setAttributes([
            .init(key: "version", value: "2.0"),
        ])
    }
}
// <rss version="2.0">...</rss> - tag with a default attribute

Attribute management

You can set, add or delete the attributes of a given tag.

Leaf("example")
    // set (override) the current attributes
    .setAttributes([
        .init(key: "a", value: "foo"),
        .init(key: "b", value: "bar"),
        .init(key: "c", value: "baz"),
    ])
    // add a new attribute using a key & value 
    .attribute("foo", "example")
    // add a new flag attribute (without a value)
    .flagAttribute("bar")
    // delete an attribute by using a key
    .deleteAttribute("b")
    
// <leaf a="foo" c="baz" foo="example" bar></leaf>

You can also manage the class atrribute through helper methods.

Span("foo")
    // set (override) class values 
    .class("a", "b", "c")
    // add new class values   
    .class(add: ["d", "e", "f"])
    // add new class value if the condition is true
    .class(add: "b", true)
    /// remove multiple class values
    .class(remove: ["b", "c", "d"])
    /// remove a class value if the condition is true
    .class(remove: "e", true)

// <span class="a f"></span>

You can create your own attribute modifier via an extension.

public extension Guid {
    
    func isPermalink(_ value: Bool = true) -> Self {
        attribute("isPermalink", String(value))
    }
}

There are other built-in type-safe attribute modifiers available on tags.

Composing tags

You can come up with your own Tag composition system by introducing a new protocol.

protocol TagRepresentable {

    func build() -> Tag
}

struct ListComponent: TagRepresentable {

    let items: [String]
    
    init(_ items: [String]) {
        self.items = items
    }

    @TagBuilder
    func build() -> Tag {
        Ul {
            items.map { Li($0) }
        }
    }
}

let tag = ListComponent(["a", "b", "c"]).build()

This way it is also possible to extend the TagBuilder to support the new protocol.

extension TagBuilder {

    static func buildExpression(_ expression: TagRepresentable) -> Tag {
        expression.build()
    }
    
    static func buildExpression(_ expression: TagRepresentable) -> [Tag] {
        [expression.build()]
    }

    static func buildExpression(_ expression: [TagRepresentable]) -> [Tag] {
        expression.map { $0.build() }
    }

    static func buildExpression(_ expression: [TagRepresentable]) -> Tag {
        GroupTag {
            expression.map { $0.build() }
        }
    }
}

Sometimes you'll need extra parameters for the build function, so you have to call the build method by hand.

In those cases it is recommended to introduce a render function instead of using build.

let tag = WebIndexTemplate(ctx) {
    ListComponent(["a", "b", "c"])
        .render(req)
}
.render(req)

If you want to create a lightweight template engine for the Vapor web framework using SwiftHtml, you can see a working example inside the Feather CMS core repository.

Credits & references

About

An awesome Swift HTML DSL library using result builders.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Swift 99.9%
  • Makefile 0.1%