Skip to content

BinaryBirds/swift-web-standards

Repository files navigation

Swift Web Standards

An awesome Swift library that closely follows the W3C web standards.

Release: 1.0.0-beta.1

Features

Swift Web Standards provides a type-safe, standards-first approach to generating web formats in Swift. It eliminates stringly-typed templates, making it ideal for server-side rendering.

Including DSL using result builders for the following:

  • HTML
  • CSS
  • Sitemap
  • RSS
  • SVG

Other abstraction to build custom types:

  • DOM to work with raw nodes
  • SGML to define custom XML formats

Type-safe, comprehensive list of:

  • MIME types

Requirements

Swift 6.1+ Platforms: macOS, iOS, tvOS, watchOS, visionOS

  • Swift 6.1+
  • Platforms:
    • macOS 15+
    • iOS 18+
    • tvOS 18+
    • watchOS 11+
    • visionOS 2+

Installation

The Swift Web Standards package is distributed through Swift Package Manager.

Add this package to your Package.swift dependencies:

.package(url: "https://github.com/binarybirds/swift-web-standards", from: "1.0.0-beta.1"),

Then include the required product as a dependency for your target:

.product(name: "HTML", package: "HTML"),

Import the module in your source files:

import HTML

The HTML package is now ready to use.

Available libraries:

  • HTML
  • CSS
  • RSS
  • SVG
  • Sitemap
  • MIME
  • WebStandards (bundles all from above)

DOM

The DOM library provides the foundational data structures used to construct and render a Node-based object tree.
This tree is composed of the following node types:

  • CommentNode — represents an HTML/XML-style comment (<!-- comment -->)
  • ListNode — a container node used to group child nodes
  • ShortNode — a void (self-closing) element representation (<node>)
  • StandardNode — a normal element with opening and closing tags (<node></node>)
  • TextNode — raw textual content within the tree

These node types form the low-level DOM representation used by the renderer.

SGML

The SGML library provides a higher-level API for defining and constructing markup languages.
It is designed to support the creation of any XML-based format—including HTML, RSS, SVG, and custom schemas.

You can define your own elements by conforming to one of the following protocols:

  • Element — the base protocol representing a generic element backed by a Node
  • Tag — a named element; inherits from Element
  • ShortTag — a named, void (self-closing) tag; inherits from Tag
  • StandardTag — a named element with both opening and closing tags; inherits from Tag

Examples

Here is a minimal example of defining a custom short tag:

public struct Br: ShortTag {

    public var attributes: AttributeStore

    public init() {
        attributes = .init()
    }
}

A standard tag can be represented as follows, including result-builder support provided by the @ElementBuilder attribute:

public struct P: StandardTag {

    public var attributes: AttributeStore
    public var children: [Element]

    public init(
        _ contents: String
    ) {
        self.attributes = .init()
        self.children = [
            Text(contents)
        ]
    }

    public init(
        children: [Element]
    ) {
        self.attributes = .init()
        self.children = children
    }

    public init(
        @ElementBuilder _ block: () -> [Element]
    ) {
        self.init(children: block())
    }
}

Custom tag names

By default, the tag name is automatically derived from the type name (converted to lowercase).
It is also possible to override the static name property manually:

struct LastBuildDate: StandardTag {
    
    static let name = "lastBuildDate"
    
    // ...
}

Attribute modifiers

You can define custom element attributes by creating a new attribute modifier protocol.

public protocol StyleAttributeModifier {
    associatedtype StyleAttributeValueType: AttributeValueRepresentable = String
}

extension StyleAttributeModifier where Self: Attributes & Mutable {

    public func style(
        _ value: StyleAttributeValueType?
    ) -> Self {
        setAttribute(
            key: "style",
            value: value?.attributeValue
        )
    }
}

You can set, add, or remove attributes—or even modify individual attribute values—on any tag that supports attributes:

P("Lorem ipsum")
    // set (override) the current attributes
    .setAttribute(Class("note"))
    .setAttributeValueBy(name: "style", value: "color: white;")
    .setAttributes([
        Alignment(.left)
    ])
    // add attribute or value(s) 
    .addAttributeValue(Class("important"))
    .addAttributeValueBy(name: "style", value: "background: black;")
    .addAttributeValues([
        Class("large")
    ])
    // remove attribute or value(s)
    .removeAttributeBy(Class.self)
    .removeAttributeBy(name: "style")
    .removeAttributeValueBy(
        Alignment(.left)
    )

There are built-in, type-safe attributes and helper modifiers available for the standard tags.

Conditions

Use the if modifier to evaluate a condition and update an element when the condition is met:

let condition = false

H1("Lorem ipsum")
    .if(condition) {
        $0.class("foo")
    } else: {
        $0.class("bar")
    }

Container elements

It is also possible to define tags that contain child elements; these are referred to as container elements.
All standard tags support child elements by default.

public struct CustomTag:
    StandardTag
{

    public var attributes: AttributeStore

    public var children: [Element]

    init(
        attributes: AttributeStore = .init(),
        children: [Element]
    ) {
        self.attributes = attributes
        self.children = children
    }

    public init(
        @Builder<Element> _ block: () -> [Element]
    ) {
        self.init(children: block())
    }

}

HTML

This produces a standards-compliant HTML document:

import HTML

let html = Html {
    Head {
        Title("Hello, SwiftHTML!")
        Meta().charset("utf-8")
        Meta().name(.viewport).content("width=device-width, initial-scale=1")
        Link(rel: .stylesheet).href("./css/style.css")
    }
    Body {
        H1("Hello, SwiftHTML!")
        Ul {
            Li("Type-safe HTML DSL for Swift 6+")
            Li("Concurrency-safety; sendable support")
            Li("Contains all the HTML tag definitions")
            Li("RSS, Sitemap, SVG support as well")
        }

        Script(#"console.log("Hello, SwiftHTML!")"#)
        Script().src("./js/main.js").async()
    }
}

let result = Document(type: .html, root: html).render(indent: 4)
print(result) // HTML output

CSS

This produces a CSS document:

let css = Stylesheet {            
    Media {
        AllElements {
            Background("#222")
        }
        Root {
            Color(.blue)
        }
        Element("div") {
            BackgroundColor(.red)
            Color(.white)
            TextAlign(.left)
        }
        .pseudo(.nthChild(2))
    }
    Media(.screen && .maxWidth(600.px)) {
        Id("custom-identifier") {
            Background("#222")
            Color(.cyan)
        }
        Class("custom-class") {
            Background("#333")
            Color(.aliceBlue)
        }
        Custom("ul > li > a:hover") {
            Background("black")
            Color(.red)
                .important()
        }
    }
}
    
print(StylesheetRenderer(minify: false, indent: 4).render(css))

DocC API documentation

API documentation is available at the following link.

Contributing

Pull requests are welcome. Please keep changes focused and include tests for new logic.

Credits & references

About

HTML, CSS and other Web Standards implemented using Swift.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors

Languages