• Stars
    star
    760
  • Rank 59,770 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 6 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ—บ A Swift DSL for type-safe, extensible, and transformable HTML documents.

๐Ÿ—บ swift-html

CI

A Swift DSL for type-safe, extensible, and transformable HTML documents.

Table of Contents

Motivation

The popular choice for rendering HTML in Swift these days is to use templating languages, but they expose your application to runtime errors and invalid HTML. Our library prevents these runtime issues at compile-time by embedding HTML directly into Swiftโ€™s powerful type system.

Examples

HTML documents can be created in a tree-like fashion, much like you might create a nested JSON document:

import Html

let document: Node = .document(
  .html(
    .body(
      .h1("Welcome!"),
      .p("Youโ€™ve found our site!")
    )
  )
)

Underneath the hood these tag functions html, body, h1, etc., are just creating and nesting instances of a Node type, which is a simple Swift enum. Because Node is just a simple Swift type, we can transform it in all kinds of interesting ways. For a silly example, what if we wanted to remove all instances of exclamation marks from our document?

func unexclaim(_ node: Node) -> Node {
  switch node {
  case .comment:
    // Don't need to transform HTML comments
    return node

  case .doctype:
    // Don't need to transform doctypes
    return node

  case let .element(tag, attrs, children):
    // Recursively transform all of the children of an element
    return .element(tag, attrs, unexclaim(children))

  case let .fragment(children):
    // Recursively transform all of the children of a fragment
    return .fragment(children.map(unexclaim))

  case let .raw(string):
    // Transform text nodes by replacing exclamation marks with periods.
    return .raw(string.replacingOccurrences(of: "!", with: "."))
  case let .text(string):
    // Transform text nodes by replacing exclamation marks with periods.
    return .text(string.replacingOccurrences(of: "!", with: "."))
  }
}

unexclaim(document)

Once your document is created you can render it using the render function:

render(document)
// <!doctype html><html><body><h1>Welcome!</h1><p>Youโ€™ve found our site!</p></body></html>

And of course you can first run the document through the unexclaim transformation, and then render it:

render(unexclaim(document))
// <!doctype html><html><body><h1>Welcome.</h1><p>Youโ€™ve found our site.</p></body></html>

Now the document is very stern and serious ๐Ÿ˜‚.

Safety

Because we are embedding our DSL in Swift we can take advantage of some advanced Swift features to add an extra layer of safety when constructing HTML documents. For a simple example, we can strengthen many HTML APIs to force their true types rather than just relying on strings.

let imgTag = Node.img(attributes: [.src("cat.jpg"), .width(400), .height(300)])

render(imgTag)
// <img src="cat.jpg" width="400" height="300">

Here the src attribute takes a string, but width and height take integers, as itโ€™s invalid to put anything else in those attributes.

For a more advanced example, <li> tags can only be placed inside <ol> and <ul> tags, and we can represent this fact so that itโ€™s impossible to construct an invalid document:

let listTag = Node.ul(
  .li("Cat"),
  .li("Dog"),
  .li("Rabbit")
) // โœ… Compiles!

render(listTag)
// <ul><li>Cat</li><li>Dog</li><li>Rabbit</li></ul>

Node.div(
  .li("Cat"),
  .li("Dog"),
  .li("Rabbit")
) // ๐Ÿ›‘ Compile error

Design

The core of the library is a single enum with 6 cases:

public enum Node {
  case comment(String)
  case doctype(String)
  indirect case element(String, [(key: String, value: String?)], Node)
  indirect case fragment([Node])
  case raw(String)
  case text(String)
}

This type allows you to express every HTML document that can ever exist. However, using this type directly can be a little unwieldy, so we provide a bunch of helper functions for constructing every element and attribute from the entire HTML spec in a type-safe manner:

// Not using helper functions
Node.element("html", [], [
  .element("body", [], [
    .element("p", [], [.text("Youโ€™ve found our site!")])
    ])
  ])

// versus

// Using helper functions
Node.html(
  .body(
    .h1("Welcome!"),
    .p("Youโ€™ve found our site!")
  )
)

This makes the โ€œSwiftificationโ€ of an HTML document looks very similar to the original document.

FAQ

Can I use this with existing Swift web frameworks like Kitura and Vapor?

Yes! We even provide plug-in libraries that reduce the friction of using this library with Kitura and Vapor. Find out more information at the following repos:

Why would I use this over a templating language?

Templating languages are popular and easy to get started with, but they have many drawbacks:

  1. Stringy APIs: Templating languages are always stringly typed because you provide your template as a big ole string, and then at runtime the values are interpolated and logic is executed. This means things we take for granted in Swift, like the compiler catching typos and type mismatches, will go unnoticed until you run the code.

  2. Incomplete language: Templating languages are just that: programming languages. That means you should expect from these languages all of the niceties you get from other fully-fledged languages like Swift. That includes syntax highlighting, IDE autocompletion, static analysis, refactoring tools, breakpoints, debugger, and a whole slew of features that make Swift powerful like let-bindings, conditionals, loops and more. However, the reality is that no templating language supports all of these features.

  3. Rigid: Templating languages are rigid in that they do not allow the types of compositions and transformations we are used to performing on data structures in Swift. It is not possible to succinctly traverse over the documents you build, and inspect or transform the nodes you visit. This capability has many applications, such as being able to pretty print or minify your HTML output, or writing a transformation that allows you to inline a CSS stylesheet into an HTML node. There are entire worlds closed off to you due to how templating languages work.

The DSL in this library fixes all of these problems, and opens up doors that are completely closed to templating languages.

When is it more appropriate to use a templating language over swift-html?

There are a few reasons you might want to still use a templating language:

  1. A designer delivers a large HTML document to you and all you want to do is hook in a little bit of value interpolation or logic. In this case you can simply copy and paste that HTML into your template, add a few interpolation tokens, and you're well on your way to having a full page served from your web application.

  2. You need to render non-HTML documents. The beauty of templating languages is that it outputs straight to plain text, and so it can model any type of document, whether it be HTML, markdown, XML, RSS, ATOM, LaTeX, and more.

  3. Creating very large documents in a single expression can cause compile times to go up, whereas templates are not compiled by Swift and so do not influence compile times. Luckily this isn't a problem too often because it is very easy to break up a document into as many small pieces as you want, which will probably lead to more reusable code in the long run.

If you do decide that a templating language better suites your needs, then you should consider HypertextLiteral, which gives you template-like capabilities but in a safer manner.

Installation

You can add swift-html to an Xcode project by adding it as a package dependency.

https://github.com/pointfreeco/swift-html

If you want to use swift-html in a SwiftPM project, it's as simple as adding it to a dependencies clause in your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-html", from: "0.4.0")
]

Interested in learning more?

These concepts (and more) are explored thoroughly in a series of episodes on Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

The ideas for this library were explored in the following episodes:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

More Repositories

1

swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
Swift
12,150
star
2

swift-snapshot-testing

๐Ÿ“ธ Delightful Swift snapshot testing.
Swift
3,737
star
3

isowords

Open source game built in SwiftUI and the Composable Architecture.
Swift
2,667
star
4

swift-navigation

Bringing simple and powerful navigation tools to all Swift platforms, inspired by SwiftUI.
Swift
1,950
star
5

swift-dependencies

A dependency management library inspired by SwiftUI's "environment."
Swift
1,527
star
6

swift-tagged

๐Ÿท A wrapper type for safer, expressive code.
Swift
1,364
star
7

swift-overture

๐ŸŽผ A library for function composition.
Swift
1,139
star
8

pointfreeco

๐ŸŽฌ The source for www.pointfree.co, a video series on functional programming and the Swift programming language.
Swift
1,098
star
9

episode-code-samples

๐Ÿ’พ Point-Free episode code.
Swift
950
star
10

swift-case-paths

๐Ÿงฐ Case paths extends the key path hierarchy to enum cases.
Swift
905
star
11

swift-parsing

A library for turning nebulous data into well-structured data, with a focus on composition, performance, generality, and ergonomics.
Swift
847
star
12

swift-nonempty

๐ŸŽ A compile-time guarantee that a collection contains a value.
Swift
839
star
13

swift-custom-dump

A collection of tools for debugging, diffing, and testing your application's data structures.
Swift
795
star
14

combine-schedulers

โฐ A few schedulers that make working with Combine more testable and more versatile.
Swift
701
star
15

swift-perception

Observable tools, backported.
Swift
536
star
16

swift-identified-collections

A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.
Swift
529
star
17

swift-web

๐Ÿ•ธ A collection of Swift server-side frameworks for handling HTML, CSS, routing and middleware.
Swift
481
star
18

swift-prelude

๐ŸŽถ A collection of types and functions that enhance the Swift language.
Swift
469
star
19

swift-validated

๐Ÿ›‚ A result type that accumulates multiple errors.
Swift
392
star
20

swift-issue-reporting

Report issues in your application and library code as Xcode runtime warnings, breakpoints, assertions, and do so in a testable manner.
Swift
361
star
21

swift-url-routing

A bidirectional router with more type safety and less fuss.
Swift
347
star
22

swift-concurrency-extras

Useful, testable Swift concurrency.
Swift
315
star
23

swift-gen

๐ŸŽฑ Composable, transformable, controllable randomness.
Swift
266
star
24

swift-macro-testing

Magical testing tools for Swift macros.
Swift
263
star
25

swift-clocks

โฐ A few clocks that make working with Swift concurrency more testable and more versatile.
Swift
258
star
26

syncups

A rebuild of Appleโ€™s โ€œScrumdingerโ€ application using modern, best practices for SwiftUI development.
Swift
205
star
27

swift-enum-properties

๐Ÿค Struct and enum data access in harmony.
Swift
200
star
28

composable-core-location

A library that bridges the Composable Architecture and Core Location.
Swift
108
star
29

vapor-routing

A bidirectional Vapor router with more type safety and less fuss.
Swift
89
star
30

swift-html-vapor

๐Ÿ’ง Vapor plugin for type-safe, transformable HTML views.
Swift
84
star
31

swift-playground-templates

๐Ÿซ A collection of helpful Xcode playground templates.
Makefile
81
star
32

pointfreeco-server

Point-Free server code.
40
star
33

TrySyncUps

The starting project for our try! Swift 2024 Composable Architecture workshop.
Swift
38
star
34

composable-core-motion

A library that bridges the Composable Architecture and Core Motion.
Swift
29
star
35

swift-boundaries

๐Ÿฃ Functional core, imperative shell.
Swift
28
star
36

swift-quickcheck

๐Ÿ An implementation of QuickCheck in Swift.
Swift
25
star
37

swift-either

For those times you want A or B!
Swift
21
star
38

swift-algebras

Algebraic laws bundled into concrete data types.
20
star
39

swift-parser-printer

โ†”๏ธ Parsing and printing
Swift
15
star
40

swift-html-kitura

โ˜๏ธ Kitura plugin for type-safe, transformable HTML views.
Swift
14
star
41

swiftui-navigation

This package is now Swift Navigation:
Swift
13
star
42

homebrew-swift

Ruby
3
star
43

swift-bugs

3
star
44

Ccmark

Swift
2
star
45

.github

1
star