macOS Linux

A collection of frameworks for solving various problems in building a Swift web framework. Each framework focuses on a single problem, like HTML rendering, CSS preprocessing, routing, middleware, and more. They also do not depend on any other framework in the collection. You can choose which pieces you want and don’t want, for example you can use Html without Css.

Stability

This library should be considered alpha, and not stable. Breaking changes will happen often.

Installation

import PackageDescription

let package = Package(
  dependencies: [
    .package(url: "https://github.com/pointfreeco/swift-web.git", .branch("master")),
  ]
)Copy the code

Getting started

This library contains an extensive test suite and a set of playgrounds that can be explored. To get it running:

  • git clone https://github.com/pointfreeco/swift-web.git
  • cd swift-web
  • swift package generate-xcodeproj
  • xed .
  • Run tests: Command+U
  • Build: Command+B
  • Open a playground!

Included modules

Primary modules

Supporting modules

Html

An embedded domain specific language (EDSL) in Swift for modeling HTML documents. A few simple value types and functions allow you to model most of HTML, and they compose easily.

import Html let document = html( [ body( [ p(["Hello world!"]), p(["Goodbye! "]), a([href("/")], ["Home"]) ] ) ] ) render(document, config: pretty)Copy the code
<html> <body> <p>Hello world! </p> <p>Goodbye! </p> <a href="/">Home</a> </body> </html>Copy the code

The design of this library has been covered by the following articles:

Css

An EDSL for a CSS preprocessor like Sass. A few simple value types and functions allow you to model most of CSS, and allow you express new things not possible in standard CSS.

Import Css let Css = body % (padding(all:.rem(2)) <> background(color.hsl (60, 0.5, 0.8))) render(Css: Css)Copy the code
body {
  padding-top    : 2rem;
  padding-right  : 2rem;
  padding-bottom : 2rem;
  padding-left   : 2rem;
  background     : #e6e6b3;
}Copy the code

HttpPipeline

A few types and functions for modeling server middleware as a simple function that transforms a request to a response. It uses phantom types express the state transitions of when you are allowed to write the status, headers and response body.

import HttpPipeline let middleware = writeStatus(.ok) >>> writeHeader(.contentType(.html)) >>> closeHeaders >>> send(render(document).data(using: .utf8)) >>> end let request = URLRequest(url: URL(string: "/")!) let conn = connection(from: request).map(const(Data? .none))Copy the code
▿ Step ResponseEnded ▿ Request GET/(Data, 0 bytes) ▿ Response Status 200 OK Content-type: text/ HTML; charset=utf8 <html><body><p>Hello world! </p><p>Goodbye! </p><a href="/">Home</a></body></html>Copy the code

ApplicativeRouter

A router built on the principles of “applicatives” that unmentioningrequests and printing routes. It is robust, composable and type-safe. Its job is to take the incoming, unstructured URLRequest from the browser and turn it into a structured value so that your app can do what it needs to do to produce a response. Additionally, given a value, it can do the reverse in which it generates a request that can be used in a hyperlink. Most of the ideas for this library were taking from this paper.

import ApplicativeRouter struct UserData: Decodable { let email: String } enum Route { case home case episodes case episode(String) case search(String?) case signup(UserData?) } let router = [// Matches: GET/route.iso.home < $end, // Matches: GET /episode/: STR route.iso. Episode <¢> GET %> lit("episode") %> pathParam(.string) <% end, // Matches: All: // Matches: GET /episodes route.iso. Episodes <¢GET %> lit("episodes") <% end, // Matches: Query =:optional_string route.iso. Search <¢> GET %> lit("search") %> queryParam("query", opt(.string)) <% end, // Matches: POST /signup route.iso.signup <¢> POST %> jsonBody(Episode. Self) <%> lit("signup") %> opt(.jsonbody)) <% end, ] .reduce(.empty, <|>) // Match a route given a request let request = URLRequest(url: URL(string: "https://www.pointfree.co/episode/001-hello-world")!) let route = router.match(request: request) // => Route.episode("001-hello-world") // Generate a string from a route: router.absoluteString(for: .episode("001-hello-world")) // => /episode/001-hello-worldCopy the code

HttpPipelineHtmlSupport

Adds middleware for rendering an Html view:

import Foundation import Html import HttpPipeline import HttpPipelineHtmlSupport let view = View(p(["Hello world!"] )) let middleware = writeStatus(.ok) >>> respond(view) let conn = connection(from: URLRequest(url: URL(string: "/")!) ) middleware(conn).response.descriptionCopy the code
Status 200 Content-Type: text/html <p>Hello world! </p>Copy the code

HtmlCssSupport

Adds an element and attribute function to Html for render Css values into an internal stylesheet or inline styles. The element function style allows you to provide a Stylesheet value that will be rendered to an internal stylesheet:

import Css
import Html
import HtmlCssSupport

let css = body % background(red)
let document = html([head([style(css)])])
render(document)Copy the code
<html>
  <head>
    <style>body{background:#ff0000}</style>
  </head>
</html>Copy the code

The attribute function style allows you to render a stylesheet inline directly on an element:

import Css
import Html
import HtmlCssSupport

let anchorStyle = color(.red)
  <> textTransform(.capitalize)

let styledDocument = p(
  [
    "Go back ",
    a([style(anchorStyle)], ["Home"])
  ]
)
print(render(styledDocument, config: pretty))Copy the code
<p> Go back <a style="color:#ff0000; text-transform:capitalize"> Home </a> </p>Copy the code

HtmlPrettyPrint

Contains functions for pretty printing an Html node (or nodes) using DoctorPretty, a wonderful little pretty printer library. The implementation of this library has been covered in this article.

The library not only takes care of adding newlines for tags so that the DOM structure is easy to read, but will also insert newlines when text goes past a column width, and even align smartly:

import HtmlPrettyPrint let doc: Node = .document( [ html( [ body( [ comment("This is gonna be a long comment. Let's see what happens! "), div( [ div([ id("some-long-id"), Html.class("foo bar baz") ], ["hello world"]), img(src: "cat.jpg", alt: "", [ id("cat"), Html.class("cat") ]) ] ) ] ) ] ) ] ) prettyPrint(node: doc, pageWidth: 40)Copy the code
<! DOCTYPE html> <html> <body> <! -- This is gonna be a long comment. Let's see what happens! --> <div> <div id="some-long-id" class="foo bar baz"> hello world </div> <img src="cat.jpg" alt="" id="cat" class="cat">  </div> </body> </html>Copy the code

CssReset

Contains a single value reset of type Stylesheet that resets all of the defaults for a webpage. It can be combined with another stylesheet via reset <> otherStyles, or can be directly rendered to a stylesheet string via render(reset).

License

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