This project explores the creation of a DSL (domain-specific language) for rendering HTML in Swift, inspired by the declarative syntax of SwiftUI.
The core UI primitive of Stitch is the Component, which is loosely analogous to the SwiftUI View. Components specify a body, which contains the UI. UI construction is declarative. Stitch components can be constructed out of any standard HTML element or any other custom Stitch element. An example of a simple home page is shown below:
struct HomePage: Component {
var body: some Component {
Div {
H1("Hello, world!")
.className("font-bold")
Br()
P {
"This is "
Span("SUPER cool.")
}
AnotherComponent()
}
}
}Currently, components are renderable, which produces raw HTML as output:
struct SmallPage: Component {
var body: some Component {
Div {
P {
"I am "
Span("renderable!")
.className("font-bold")
}
}
}
}
SmallPage().render()
// Outputs:
// <div><p>I am <span class="font-bold">renderable!</span></p></div>The vision for the Stitch components is that they would eventually support reactivity, including states (@State), handling side effects (@Effect), more complex querying (@Query) and event handling.
Conditional rendering is supported, like so:
struct ConditionalRendering: Component {
var body: some Component {
Div {
if myCondition {
P("It's true!")
} else {
P("It's false...")
}
}
}
}
SmallPage().render()
// Outputs:
// <div><p>It's true!</p></div>Building out HTML with loops is also supported, like so:
struct IterableRendering: Component {
var items = ["one", "two", "three"]
var body: some Component {
Div {
for item in items {
P(item)
}
}
}
}
SmallPage().render()
// Outputs:
// <div><p>one</p><p>two</p><p>three</p></div>All standard HTML elements exist in Stitch. There are two ways of creating elements:
- If an element is a one-liner (only contains text or no content), then a text constructor can be used:
Br()
P("Hello, world!")
H1("I 💛 Swift")- If an element composes other elements or contains a more complex body, the body (
@ComponentBuilder) constructor can be used:
Div {
P("Wow")
P {
"This is "
Span("EPIC")
"!"
}
}Strings and elements can be mixed and matched in the same body.
Currently, attributes to elements are applied using modifiers, in a similar way to how modifiers are applied to SwiftUI views. For example, to apply a class to a paragraph (P), you would use the .className() modifier. Here is an example (using Tailwind-esque styling):
P("Hello, world!")
.className("text-muted-foreground")Some HTML elements have required inputs. These are passed as parameters:
Video(src: "/..path")In the future, HTML elements in Stitch will be modified so that some non-required inputs for certain elements are passed in parameters where it is applicable.
The core functionality for Stitch's declarative view creation is @ComponentBuilder, which is inspired by SwiftUI's @ViewBuilder. @ComponentBuilder is built on top of result builders, one of my favorite features in Swift.
With the help of Swift's function builder, @ComponentBuilder can support blocks with multiple components, conditional rendering (with if and if-else), and views built with for-loops. There is also a helper GroupComponent to help with managing groupings of components.
The full @ComponentBuilder functionality can be found here.