SkillAgentSearch skills...

Plot

A DSL for writing type-safe HTML, XML and RSS in Swift.

Install / Use

/learn @JohnSundell/Plot
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <img src="Logo.png" width="400" max-width="90%" alt="Plot" /> </p> <p align="center"> <img src="https://img.shields.io/badge/Swift-5.4-orange.svg" /> <a href="https://swift.org/package-manager"> <img src="https://img.shields.io/badge/swiftpm-compatible-brightgreen.svg?style=flat" alt="Swift Package Manager" /> </a> <img src="https://img.shields.io/badge/platforms-mac+linux-brightgreen.svg?style=flat" alt="Mac + Linux" /> <a href="https://twitter.com/johnsundell"> <img src="https://img.shields.io/badge/twitter-@johnsundell-blue.svg?style=flat" alt="Twitter: @johnsundell" /> </a> </p>

Welcome to Plot, a domain-specific language (DSL) for writing type-safe HTML, XML and RSS in Swift. It can be used to build websites, documents and feeds, as a templating tool, or as a renderer for higher-level components and tools. It’s primary focus is on static site generation and Swift-based web development.

Plot is used to build and render all of swiftbysundell.com.

Write HTML — in Swift!

Plot enables you to write HTML using native, fully compiled Swift code, by modeling the HTML5 standard’s various elements as Swift APIs. The result is a very lightweight DSL that lets you build complete web pages in a highly expressive way:

let html = HTML(
    .head(
        .title("My website"),
        .stylesheet("styles.css")
    ),
    .body(
        .div(
            .h1("My website"),
            .p("Writing HTML in Swift is pretty great!")
        )
    )
)

Looking at the above, it may at first seem like Plot simply maps each function call directly to an equivalent HTML element — and while that’s the case for some elements, Plot also inserts many kinds of highly valuable metadata automatically. For example, the above expression will result in this HTML:

<!DOCTYPE html>
<html>
    <head>
        <title>My website</title>
        <meta name="twitter:title" content="My website"/>
        <meta property="og:title" content="My website"/>
        <link rel="stylesheet" href="styles.css" type="text/css"/>
    </head>
    <body>
        <div>
            <h1>My website</h1>
            <p>Writing HTML in Swift is pretty great!</p>
        </div>
    </body>
</html>

As you can see above, Plot added both all of the necessary attributes to load the requested CSS stylesheet, along with additional metadata for the page’s title as well — improving page rendering, social media sharing, and search engine optimization.

Plot ships with a very wide coverage of the HTML5 standard, enabling all sorts of elements to be defined using the same lightweight syntax — such as tables, lists, and inline text styling:

let html = HTML(
    .body(
        .h2("Countries and their capitals"),
        .table(
            .tr(.th("Country"), .th("Capital")),
            .tr(.td("Sweden"), .td("Stockholm")),
            .tr(.td("Japan"), .td("Tokyo"))
        ),
        .h2("List of ", .strong("programming languages")),
        .ul(
            .li("Swift"),
            .li("Objective-C"),
            .li("C")
        )
    )
)

Above we’re also using Plot’s powerful composition capabilities, which lets us express all sorts of HTML hierarchies by simply adding new elements as comma-separated values.

Applying attributes

Attributes can also be applied the exact same way as child elements are added, by simply adding another entry to an element’s comma-separated list of content. For example, here’s how an anchor element with both a CSS class and a URL can be defined:

let html = HTML(
    .body(
        .a(.class("link"), .href("https://github.com"), "GitHub")
    )
)

The fact that attributes, elements and inline text are all defined the same way both makes Plot’s API easier to learn, and also enables a really fast and fluid typing experience — as you can simply type . within any context to keep defining new attributes and elements.

Type safety built-in

Plot makes heavy use of Swift’s advanced generics capabilities to not only make it possible to write HTML and XML using native code, but to also make that process completely type-safe as well.

All of Plot’s elements and attributes are implemented as context-bound nodes, which both enforces valid HTML semantics, and also enables Xcode and other IDEs to provide rich autocomplete suggestions when writing code using Plot’s DSL.

For example, above the href attribute was added to an <a> element, which is completely valid. However, if we instead attempted to add that same attribute to a <p> element, we’d get a compiler error:

let html = HTML(.body(
    // Compiler error: Referencing static method 'href' on
    // 'Node' requires that 'HTML.BodyContext' conform to
    // 'HTMLLinkableContext'.
    .p(.href("https://github.com"))
))

Plot also leverages the Swift type system to verify each document’s element structure as well. For example, within HTML lists (such as <ol> and <ul>), it’s only valid to place <li> elements — and if we break that rule, we’ll again get a compiler error:

let html = HTML(.body(
    // Compiler error: Member 'p' in 'Node<HTML.ListContext>'
    // produces result of type 'Node<Context>', but context
    // expects 'Node<HTML.ListContext>'.
    .ul(.p("Not allowed"))
))

This high degree of type safety both results in a really pleasant development experience, and that the HTML and XML documents created using Plot will have a much higher chance of being semantically correct — especially when compared to writing documents and markup using raw strings.

Components

Plot’s Component protocol enables you to define and render higher-level components using a very SwiftUI-like API. Node and Component-based elements can be mixed when creating an HTML document, giving you the flexibility to freely choose which way to implement which part of a website or document.

For example, let’s say that we’re building a news website using Plot, and that we’d like to render news articles in several different places. Here’s how we could define a reusable NewsArticle component that in turn uses a series of built-in HTML components to render its UI:

struct NewsArticle: Component {
    var imagePath: String
    var title: String
    var description: String

    var body: Component {
        Article {
            Image(url: imagePath, description: "Header image")
            H1(title)
            Span(description).class("description")
        }
        .class("news")
    }
}

As the above example shows, modifiers can also be applied to components to set the value of attributes, such as class or id.

To then integrate the above component into a Node-based hierarchy, we can simply wrap it within a Node using the .component API, like this:

func newsArticlePage(for article: NewsArticle) -> HTML {
    return HTML(.body(
        .div(
            .class("wrapper"),
            .component(article)
        )
    ))
}

You can also directly inline Node-based elements within a component’s body, which gives you complete freedom to mix and match between the two APIs:

struct Banner: Component {
    var title: String
    var imageURL: URLRepresentable

    var body: Component {
        Div {
            Node.h2(.text(title))
            Image(imageURL)
        }
        .class("banner")
    }
}

It’s highly recommended that you use the above component-based approach as much as possible when building websites and documents with Plot — as doing so will let you build up a growing library of reusable components, which will most likely accelerate your overall workflow over time.

However, note that the Component API can currently only be used to define elements that appear within the <body> of an HTML page. For <head> elements, or non-HTML elements, the Node-based API always has to be used.

Another important note is that, although Plot has been heavily optimized across the board, Component-based elements do require a bit of extra processing compared to Node-based ones — so in situations where maximum performance is required, you might want to stick to the Node-based API.

Using the component environment

Just like SwiftUI views, Plot components can pass values downwards through a hierarchy using an environment API. Once a value has been entered into the environment using an EnvironmentKey and the environmentValue modifier, it can then be retrieved by defining a property marked with the @EnvironmentValue attribute within a Component implementation.

In the following example, the environment API is used to enable a Page component to assign a given class to all ActionButton components that appear within its hierarchy:

// We start by defining a custom environment key that can be
// used to enter String values into the environment:
extension EnvironmentKey where Value == String {
    static var actionButtonClass: Self {
        Self(defaultValue: "action-button")
    }
}

struct Page: Component {
    var body: Component {
        Div {
            InfoView(title: "...", text: "...")
        }
        // Here we enter a custom action button class
        // into the environment, which will apply to
        // all child components within our above Div:
        .environmentValue("action-button-large",
            key: .actionButtonClass
        )
    }
}

// Our info view doesn't have to have any awareness of
// our environment value. Plot will automatically pass
// it down to the action buttons defined below:
struct InfoView: Component {
    var title: String
    var text: String

    var body: Component {
        Div {
            H2(title)
            Paragraph(text)
            ActionButton(title: "OK")
            ActionButton(title: "Cancel")
        }
        .class("info-view")
    }
}

struct ActionButton: Component {
    var

Related Skills

View on GitHub
GitHub Stars2.0k
CategoryContent
Updated4d ago
Forks143

Languages

Swift

Security Score

100/100

Audited on Mar 24, 2026

No findings