Contents

chrstphrknwtn/Hypertext

A Swift library for writing HTML with a declarative, SwiftUI-like syntax.

Goals

  • Simple API for effortless HTML authoring
  • Long-term API stability
  • Zero dependencies

Non-goals

  • HTML validation

Installation

Add Hypertext to your Package.swift:

dependencies: [
	.package(url: "https://github.com/chrstphrknwtn/Hypertext.git", from: "0.3.0")
]

Then add it as a dependency to your target:

.target(
	name: "YourTarget",
	dependencies: ["Hypertext"]
)

Requirements

  • Swift 6.0+
  • macOS, Linux

`HTMLComponent` and `HTMLPage`

Hypertext provides two protocols.

  • HTMLComponent is the building block for reusable pieces of HTML.
  • HTMLPage extends it for full documents you can send as HTTP responses.

HTMLComponent

struct NavBar: HTMLComponent {
	let links: [(label: String, href: String)] = [
		("Home", "/"),
		("About", "/about"),
		("Contact", "/contact"),
	]

	var content: Node {
		Nav(.class("navbar")) {
			for link in links {
				A(href: link.href) {
					link.label
				}
			}
		}
	}
}

HTMLPage

struct HomePage: HTMLPage {
	var lang: String? { "en" }

	var head: Node {
		Title { "My webpage!" }
		Link(rel: "stylesheet", href: "/styles.css")
	}

	var body: Node {
		NavBar()
		H1 { "Welcome to my webpage!" }
	}
}

Alternatively you can construct your own HTML document:

struct HomePage: HTMLComponent {
	var content: Node {
		Doctype()
		Html(lang: "en") {
			Head {
				Meta(charset: "utf-8")
			}
			Body {
				H1 { "Hello, world!" }
			}
		}
	}
}

Attributes

Every HTML attribute is available via dot-syntax:

Div(.id("main"), .class("container"), .hidden()) {
	A(.href("/about"), .target("_blank")) { "About" }
}

Many elements also have named arguments for their most common attributes:

Link(rel: "stylesheet", href: "/styles.css")

A(href: "/about", target: "_blank", rel: "noopener") { "About" }

Form(action: "/submit", method: "post") {
	Input(type: "email", name: "email", placeholder: "you@example.com")
	Button(type: "submit") { "Send" }
}

Hypertext doesn't enforce which attributes belong on which elements.

Classes

Pass strings, conditionals, or both:

// Simple
Div(.class("container")) { "Hello" }

// Multiple
Div(.class("container", "main")) { "Hello" }

// Conditional
Div(.class("nav-link", ["active": isActive, "disabled": isDisabled])) { "Hello" }

If an element has multiple .class() attributes they're merged automatically into the first class attribute instance:

Div(.class("card"), .id("main"), .data("active", "true"), .class(["highlighted": isActive])) { "Hello" }
<div class="card highlighted" id="main" data-active="true">Hello</div>

Style and Script

Style and Script render their content as raw text with no HTML escaping. CSS and JavaScript work as expected:

Style { "body { margin: 0; }" }
// <style>body { margin: 0; }</style>

Script { "console.log('hello')" }
// <script>console.log('hello')</script>

Script(src: "/app.js", .defer(), .async())
// <script src="/app.js" defer async></script>

Note: <style> and <script> content is not escaped. Never pass untrusted user input to these closures.

Raw HTML

Hypertext escapes text content by default. When you need to pass through a raw HTML string, use UnsafeHTML. Useful for SVG or content from a markdown renderer.

UnsafeHTML("<script>alert('Hey!')</script>")
// <script>alert('Hey!')</script>

Rendering

Use .render() to return a String:

let html = HomePage().render()

License

Apache 2.0

Package Metadata

Repository: chrstphrknwtn/Hypertext

Stars: 2

Forks: 0

Open issues: 0

Default branch: main

Primary language: swift

License: Apache-2.0

Topics: swift, swift-on-server

README: README.md