πŸ“„ A Swift DSL for writing type-safe HTML/CSS in SwiftUI way

Overview

πŸ“„ swift-web-page (swep)

Swift Version: 5.1 Swift Package Manager Swift Package Manager

Swep is a Swift DSL for writing type-safe HTML/CSS in SwiftUI way.

Table of Contents

Motivation

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

Examples

HTML/CSS documents can be created in a SwiftUI-like fashion, much like you might create a nested SwiftUI View:

import Swep

let page = document {
  html {
    head(title("YOLO!"))
    body {
      h1("Welcome!")
      p("You've found our site!")
    }
  }
}

CSS inside Style tag!

import Swep

let page = document {
  html {
    head {
      title("YOLO!")
      style {
        selector("h1") {
          color(.tomato())
        }
      }
    }
    body {
      h1("Welcome!")
      p("You've found our site!")
    }
  }
}

Even better, CSS as tag modifier

import Swep

let page = document {
  html {
    head(title("YOLO!"))
    body {
      h1("Welcome!")
        .color(.tomato())
      p("You've found our site!")
    }
  }
}

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

/// when you just want to see the output..
page.render(.debug(.pretty(.spaces(2))))
// or
page.debugRender()
<!DOCTYPE html>
<html>
  <head>
    <title>
      YOLO!
    </title>
  </head>
  <body>
    <h1 style="color:rgba(255,99,71,1.0)">
      Welcome!
    </h1>
    <p>
      You've found our site!
    </p>
  </body>
</html>
/// when your document is ready for release..
page.render(.release(.inline))
// or
page.render()
<!DOCTYPE html><html><head><title>YOLO!</title></head><body><h1 style="color:rgba(255,99,71,1.0);">Welcome!</h1><p>You&apos;ve found our site!</p></body></html>

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/CSS documents. For a simple example, we can strengthen many HTML/CSS APIs to force their true types rather than just relying on strings.

let imgTag = img()
               .src("cat.jpg")
               .width(400)
               .height(300)

imgTag.render()
// <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 = ul {
  li("Cat")
  li("Dog")
  li("Rabbit")
} // βœ… Compiles!

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

div {
  li("Cat")
  li("Dog")
  li("Rabbit")
} // πŸ›‘ Compile error

Another advance example, <thead>, <tbody>, and <tfoot> tags can only be placed inside <table> tags, same for <th>, and <td> tags can only be placed inside <tr> tags.

let tableTag = table {
  tr(th("A Head"))
  tr(td("A Body"))
} // βœ… Compiles!

type(of: tableTag)
// Table<Tuple<(Tr<Th<Text>>, Tr<Td<Text>>)>>

let tableTag = table {
  thead(tr(th("A Head")))
  tbody(tr(td("A Body")))
} // βœ… Compiles!

type(of: tableTag)
// Table<Tuple<(Thead<Tr<Th<Text>>>, Tbody<Tr<Td<Text>>>)>>

let tableTag = table {
  thead(tr(th("A Head")))
  for _ in 1...3 {
    tbody(tr(td("A Body")))
  }
} // βœ… Compiles!

type(of: tableTag)
// Table<Tuple<(Thead<Tr<Th<Text>>>, Array<Tbody<Tr<Td<Text>>>>)>>

table {
  tbody(tr(td("A Body")))
  thead(tr(th("A Head")))
} // πŸ›‘ Compile error

There alot more..

Design

Behind the scenes Swep is following the Protocol-Oriented-Programming (POP) approach, along with the powerful swift-feature @resultBuilder. There are two main-libraries Html handles the html-side, and Css handles the css-side. And one micro-library HtmlCssSupport which handles the combination of both Html, and Css.

FAQ

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

Yes! We even provide plug-in libraries that reduce the friction of using this library with Vapor, Kitura, and Perfect. 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/CSS output, or writing a transformation that allows you to inline a CSS stylesheet into an HTML/CSS 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-web-page?

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

  1. A designer delivers a large HTML/CSS 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/CSS 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/CSS 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/CSS, 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.

Real world example

Creating a html document, and css stylesheet

let titillimFont: StaticString = """
  https://fonts.googleapis.com/css2?family=\
  Titillium+Web:ital,[email protected],200;0,300;0,400;\
  0,600;0,700;0,900;1,200;1,300;1,400;1,600;\
  1,700&display=swap
  """
  
let plainStyle = style {
  `import`(titillimFont)
  selector("*, *::before, *::after") {
    margin(.px(0))
    padding(.px(0))
    boxSizing(.borderBox)
  }
  selector("body") {
    margin(.px(0), .auto)
    backgroundColor(.hex(0x111))
    fontFamily("'Titillium Web', sans-serif")
  }
}

let page = document {
  html {
    head {
      title("Hello, Swep!")
      plainStyle
    }
    body {
      h1("πŸ“„ swift-web-page (swep)")
      hr()
        .width(50%)
        .minWidth(.px(720))
        .color(.hex(0x323232))
      p {
        strong("Swep ")
        text("is a Swift DSL for writing type-safe HTML/CSS in declarative way.")
      }
      p {
        text("With ")
        strong("Swep:")
      }
      ul {
        li("You can write html documents along with css")
          .fontWeight(.bolder)
        li("Bringing all swift-language features out of the box")
        #if swift(>=5.1)
          for version in 1...4 {
            if version != 2 {
              li("supporting swift v5.\(version)")
            }
          }
        #else
          li("Unfortunately this library is built using @resultBuilder which is available in swift v5.1 and higher 😒")
        #endif
      }
      blockquote("Enjoy! ✌️😁")
    }
  }
}

Checking out the type of the document..

type(of: page.content)
Html<Tuple<(Head<Tuple<(Title<Text>, Style<Stylesheet>)>>, Body<Tuple<(H1<Text>, Hr, P<Tuple<(Strong<Text>, Text)>>, P<Tuple<(Text, Strong<Text>)>>, Ul<Tuple<(Li<Text>, Li<Text>, Array<Optional<Li<Text>>>)>>, Blockquote<Text>)>>)>>

Rendering out the document..

let renderMode: RenderMode = .release(.pretty(.spaces(2)))
page.render(renderMode)
<!DOCTYPE html>
<html>
  <head>
    <title>
      Hello, Swep!
    </title>
    <style>
      @import https://fonts.googleapis.com/css2?family=Titillium+Web:ital,[email protected],200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap;
      *, *::before, *::after {
        margin: 0px;
        padding: 0px;
        -webkit-box-sizing: border-box;
        -moz-box-sizing: border-box;
        box-sizing: border-box;
      }
      body {
        margin: 0px auto;
        background-color: #111;
        font-family: 'Titillium Web', sans-serif;
      }
    </style>
  </head>
  <body>
    <h1>
      πŸ“„ swift-web-page (swep)
    </h1>
    <hr style="width:50%;
               min-width:720px;
               color:#323232">
    <p>
      <strong>
        Swep 
      </strong>
      is a Swift DSL for writing type-safe HTML/CSS in declarative way.
    </p>
    <p>
      With 
      <strong>
        Swep:
      </strong>
    </p>
    <ul>
      <li style="font-weight:bolder">
        You can write html documents along with css
      </li>
      <li>
        Bringing all swift-language features out of the box
      </li>
      <li>
        supporting swift v5.1
      </li>
      <li>
        supporting swift v5.3
      </li>
      <li>
        supporting swift v5.4
      </li>
    </ul>
    <blockquote>
      Enjoy! ✌️😁
    </blockquote>
  </body>
</html>

Installation

Swift Package Manager (SPM)

If you want to use swift-web-page in a project that uses SPM, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
  .package(url: "https://github.com/alja7dali/swift-web-page.git", from: "0.0.1")
]

From there you can add Swep as target dependencies.

let Swep: Target.Dependency = .product(name: "Swep", package: "swift-web-page")
...
targets: [
  .target(name: "yourProject", dependencies: [Swep]),
]

License

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

You might also like...
Generate styled SwiftUI Text from strings with XML tags.
Generate styled SwiftUI Text from strings with XML tags.

XMLText is a mini library that can generate SwiftUI Text from a given XML string with tags. It uses AttributedString to compose the final text output.

Swift minion for simple and lightweight XML parsing

AEXML Swift minion for simple and lightweight XML parsing I made this for personal use, but feel free to use it or contribute. For more examples check

CheatyXML is a Swift framework designed to manage XML easily

CheatyXML CheatyXML is a Swift framework designed to manage XML easily. Requirements iOS 8.0 or later tvOS 9.0 or later Installation Cocoapods If you'

Simple XML parsing in Swift
Simple XML parsing in Swift

SWXMLHash SWXMLHash is a relatively simple way to parse XML in Swift. If you're familiar with NSXMLParser, this library is a simple wrapper around it.

Easy XML parsing using Codable protocols in Swift

XMLCoder Encoder & Decoder for XML using Swift's Codable protocols. This package is a fork of the original ShawnMoore/XMLParsing with more features an

WKZombie is a Swift framework for iOS/OSX to navigate within websites and collect data without the need of User Interface or API, also known as Headless browser. It can be used to run automated tests / snapshots and manipulate websites using Javascript. Simple XML Parser implemented in Swift
Simple XML Parser implemented in Swift

Simple XML Parser implemented in Swift What's this? This is a XML parser inspired by SwiftyJSON and SWXMLHash. NSXMLParser in Foundation framework is

A lightweight CSS parser for parsing and creating CSS stylesheets

SwiftCSSParser A lightweight CSS parser for Swift that uses cssparser (cpp) under the hood. Basic usage Here's a simple code snippet to get you starte

Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

A fast & lightweight XML & HTML parser in Swift with XPath & CSS support

Fuzi (斧子) A fast & lightweight XML/HTML parser in Swift that makes your life easier. [Documentation] Fuzi is based on a Swift port of Mattt Thompson's

SwiftSoup: Pure Swift HTML Parser, with best of DOM, CSS, and jquery (Supports Linux, iOS, Mac, tvOS, watchOS)
SwiftSoup: Pure Swift HTML Parser, with best of DOM, CSS, and jquery (Supports Linux, iOS, Mac, tvOS, watchOS)

SwiftSoup is a pure Swift library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very conveni

SwiftSoup: Pure Swift HTML Parser, with best of DOM, CSS, and jquery (Supports Linux, iOS, Mac, tvOS, watchOS)
SwiftSoup: Pure Swift HTML Parser, with best of DOM, CSS, and jquery (Supports Linux, iOS, Mac, tvOS, watchOS)

SwiftSoup is a pure Swift library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very conveni

Mongrel is a Swift and HTML hybrid with a bit of support for CSS and Javascript.

Mongrel is a Swift and HTML hybrid with a bit of support for CSS and Javascript. Using a declaritive style of programming, Mongrel makes writing HTML feel natural and easy. Mongrel also uses a SwiftUI like body structure allowing structs to be completely dedicated as an HTML page or element.

An awesome Swift CSS DSL library using result builders.

An awesome Swift CSS DSL library using result builders.

An awesome Swift HTML DSL library using result builders.

SwiftHtml An awesome Swift HTML DSL library using result builders. let doc = Document(.html5) { Html { Head { Meta()

A result builder that build HTML parser and transform HTML elements to strongly-typed result, inspired by RegexBuilder.

HTMLParserBuilder A result builder that build HTML parser and transform HTML elements to strongly-typed result, inspired by RegexBuilder. Note: Captur

Frank is a DSL for quickly writing web applications in Swift

Frank Frank is a DSL for quickly writing web applications in Swift with type-safe path routing. Sources/main.swift import Frank // Handle GET request

Frank is a DSL for quickly writing web applications in Swift

Frank Frank is a DSL for quickly writing web applications in Swift with type-safe path routing. Sources/main.swift import Frank // Handle GET request

MemoryCache - type-safe, thread-safe memory cache class in Swift

MemoryCache is a memory cache class in swift. The MemoryCache class incorporates LRU policies, which ensure that a cache doesn’t

Comments
  • SwiftUI-style encapsulation

    SwiftUI-style encapsulation

    Being able to encapsulate things like SwiftUI's View would be very helpful.

    Slightly confusing to get it working, but we have been trying a solution like this (for Vapor):

    import Swep
    import Vapor
    
    extension HTMLView {
        func encodeResponse(for request: Request) async throws -> Response {
            var headers = HTTPHeaders()
            headers.add(name: .contentType, value: "text/html; charset=utf-8")
            let view = Layout(app: request.application, content: self)
            return .init(
                headers: headers,
                body: .init(string: view.render())
            )
        }
    }
    
    typealias HTMLContent = HtmlNode & ChildOfAny
    
    protocol HTMLView: HtmlNode, AsyncResponseEncodable {
        associatedtype Content: HTMLContent
        @HtmlNodeBuilder var body: Content { get }
    }
    
    extension HTMLView {
        func render<T>(_ mode: RenderMode, into target: inout T) where T: TextOutputStream {
            body.render(mode, into: &target)
        }
    }
    
    struct SimpleHTMLView<Content: HTMLContent>: HTMLView {
        internal init(@HtmlNodeBuilder content: @escaping () -> Content) {
            self.content = content
        }
    
        let content: () -> Content
        var body: some HTMLContent {
            content()
        }
    }
    
    opened by wilg 3
Releases(0.0.1)
Owner
Abdullah Aljahdali
i failed the turing test!
Abdullah Aljahdali
SwiftSoup: Pure Swift HTML Parser, with best of DOM, CSS, and jquery (Supports Linux, iOS, Mac, tvOS, watchOS)

SwiftSoup is a pure Swift library, cross-platform (macOS, iOS, tvOS, watchOS and Linux!), for working with real-world HTML. It provides a very conveni

Nabil Chatbi 3.7k Jan 6, 2023
Mongrel is a Swift and HTML hybrid with a bit of support for CSS and Javascript.

Mongrel is a Swift and HTML hybrid with a bit of support for CSS and Javascript. Using a declaritive style of programming, Mongrel makes writing HTML feel natural and easy. Mongrel also uses a SwiftUI like body structure allowing structs to be completely dedicated as an HTML page or element.

Nicholas Bellucci 12 Sep 22, 2022
A sensible way to deal with XML & HTML for iOS & macOS

Ono (ζ–§) Foundation lacks a convenient, cross-platform way to work with HTML and XML. NSXMLParser is an event-driven, SAX-style API that can be cumbers

Mattt 2.6k Dec 14, 2022
Ji (戟) is an XML/HTML parser for Swift

Ji 戟 Ji (戟) is a Swift wrapper on libxml2 for parsing XML/HTML. Features Build XML/HTML Tree and Navigate. XPath Query Supported. Comprehensive Unit T

HongHao Zhang 824 Dec 15, 2022
Kanna(鉋) is an XML/HTML parser for Swift.

Kanna(鉋) Kanna(鉋) is an XML/HTML parser for cross-platform(macOS, iOS, tvOS, watchOS and Linux!). It was inspired by Nokogiri(ι‹Έ). ℹ️ Documentation Fea

Atsushi Kiwaki 2.3k Dec 31, 2022
Swift package to convert a HTML table into an array of dictionaries.

Swift package to convert a HTML table into an array of dictionaries.

null 1 Jun 18, 2022
Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Pavel Sharanda 1.1k Dec 26, 2022
An Objective-C framework for your everyday HTML needs.

HTMLKit An Objective-C framework for your everyday HTML needs. Quick Overview Installation Parsing The DOM CSS3 Selectors Quick Overview HTMLKit is a

Iskandar Abudiab 229 Dec 12, 2022
The most swifty way to deal with XML data in swift 5.

SwiftyXML SwiftyXML use most swifty way to deal with XML data. Features Infinity subscript dynamicMemberLookup Support (use $ started string to subscr

Kevin 99 Sep 6, 2022
A simple way to map XML to Objects written in Swift

XMLMapper XMLMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from XML. Ex

Giorgos Charitakis 109 Jan 6, 2023