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.
HTMLComponentis the building block for reusable pieces of HTML.HTMLPageextends 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