Swift HTMLKit: The only HTML library you should use

Write HTML using Swift Macrosthe first of its kind.

Oh look, another Swift HTML DSL. What makes this one special?

Here are a few reasons:

  • Swift Macros essentially eliminate any runtime overhead due to their compile-time nature
  • HTML macros enforce compile-time safety, can be used anywhere, and compile directly to strings (optionally StaticStrings) which are easily manipulated
  • The output is minified at no performance cost

Syntax

// <div class="dark"><p>Macros are beautiful</p></div>
#div(attributes: [.class(["dark"])], [
    #p(["Macros are beautiful"])
])

// <a href="https://github.com/RandomHashTags/litleagues" target="_blank"></a>
#a(href: "https://github.com/RandomHashTags/litleagues", target: ._blank)

// <input id="funny-number" max="420" min="69" name="funny_number" step="1" type="number" value="69">
#input(
    attributes: [.id("funny-number")],
    max: 420,
    min: 69,
    name: "funny_number",
    step: 1,
    type: .number,
    value: "69"
)

The README has more information and benchmarks comparing performance with 4 popular HTML libraries (Elementary, Plot, Swift Html, HTMLKit) with more coming very soon (and more complex HTML tests).

Benchmark Spoiler

This library is the clear winner in all aspects. Performance pushed to the absolute limits of the Swift language.

A 1.0.0 release is just around the corner, and I plan to use it in production at https://litleagues.com (currently uses Leaf) beginning next month. I saw a minimum of 10ms performance improvement to page loading using this library over Leaf even for a small website (probably due to minification | plus no extra disk reads).

Check it out: GitHub - RandomHashTags/swift-htmlkit: Write HTML using Swift Macros.

Let me know what you think and how I can improve it to support your workflow.

7 Likes

Congrats on getting this out the door, and welcome to the club of people trying to do HTML well with swift (I am the guy doing elmentary).

The benchmarks setup is interesting, thanks for setting that up!

One thing I am honestly interested to see is the performance of serving this from a server, as in: How long does it take for a realistically sized (a few KB), somewhat dynamic page to arrive at the browser.

In Elementary I try to optimize for the "getting bytes on the wire" case, not so much the "collect it all in a String in memory really fast" case, and I am just curious if that actually manifests or is just an esoteric exercise :thinking:.

And another thought that popped into my head is the question of HTML-escaping texts and attribute values (ie: quote out the < > & " characters). To me that is an important feature, so dynamic values don't accidentally break the HTML. (Also makes the benchmarks less meaningful, since clearly this adds extra work).

Lastly, just some friendly advice from an old man:

You might want dial down the competitive wording a bit. All the packages you mention were provided and maintained by enthusiastic people, for free, to benefit the community, asking nothing in return. There don't have to be winners and losers here.

13 Likes

I appreciate your feedback. I plan on testing and benchmarking the performance of dynamic (and larger) pages before 1.0.0. I never thought about escaping HTML but will definitely implement a solution.

However there are winners and losers when it comes to performance. I understand where you're coming from and will be dialing down the verbiage.

Interesting that you could use macros for stuff you would normally use result builders for.

1 Like

v0.6.0 has been released.

Most notably it contains a syntax change when declaring innerHTML elements, new string interpolation compiler warning, new escaping HTML feature, dynamic benchmarks (and more libraries tested), and cooling of README verbiage.

Read more here: Release 0.6.0 · RandomHashTags/swift-htmlkit · GitHub

I don't think I can improve the dynamic throughput without significant changes (usage of unsafe buffer pointers) or new features to the language (maybe a ~Copyable & ~Escapable type).

I might add markdown, xhtml and xml to the library before 1.0.0, but I don't know yet.

1 Like

v0.7.0 has been released.

Mainly a QOL update.

Most notably it contains new html macros that return different value types (like ByteBuffer, [UInt8] or [UInt16]), better interpolation flattening, addition of @taylorswift's SwiftDOM project to benchmarks, README updates, and funding info.

@sliemeobn also made a contribution updating the Elementary benchmark implementation.

Read more here: Release 0.7.0 · RandomHashTags/swift-htmlkit · GitHub

Expecting a 1.0.0 release in the first or second week of November.
I will cease posting in this thread about future minor updates.

1 Like

v0.9.0 has been released.

Mainly fixes some incorrect attribute behaviors and adds missing attributes for certain elements.

Also adds support for HTMX via global attributes!

Read more here: Release 0.9.0 · RandomHashTags/swift-htmlkit · GitHub

I want to look over the code a few more times to make sure it is feature complete and nothing breaks before a 1.0.0 release.

1 Like

v0.10.0 has been released.

This one replaces the 100+ html element macros with a single macro. Also replaces the macros for different data representations with a new HTMLEncoding parameter for the new #html macro.

The main reason behind this change is due to very poor code completion and compilation performance in projects that use this project extensively, which even I personally experienced. Plus it no longer pollutes the global namespace with over 100 related macros.

This update also allows developers to directly call the parsing code via HTMLKitUtilities.parseArguments (and the escape html logic), which allows custom macros to incorporate the HTML expansions, which is especially useful if you want to fully implement a compile time solution for your custom static content (I know I do!).

Read more here: Release 0.10.0 · RandomHashTags/swift-htmlkit · GitHub

Below is example usages of the the new html macro.

// <div class="dark"><p>Macros are beautiful</p></div>
#html(
  div(attributes: [.class(["dark"])],
    p("Macros are beautiful")
  )
)

// <a href="https://github.com/RandomHashTags/litleagues" target="_blank"></a>
#html(
  a(href: "https://github.com/RandomHashTags/litleagues", target: ._blank)
)

// <input id="funny-number" max="420" min="69" name="funny_number" step="1" type="number" value="69">
#html(
  input(
    attributes: [.id("funny-number")],
    max: 420,
    min: 69,
    name: "funny_number",
    step: 1,
    type: .number,
    value: "69"
  )
)

// html example
let test:String = #html(
  html(
    body(
      div(
          attributes: [
              .class(["dark-mode", "row"]),
              .draggable(.false),
               hidden(.true),
              .inputmode(.email),
              .title("Hey, you're pretty cool")
          ],
          "Random text",
          div(),
          a(
              div(
                  abbr()
              ),
              address()
          ),
          div(),
          button(disabled: true),
          video(autoplay: true, controls: false, preload: .auto, src: "https://github.com/RandomHashTags/litleagues", width: .centimeters(1)),
      )
    )
  )
)

v0.11.0 has been released.

3 main bug fixes in this release (1 hazardous bug fixed):

  • custom element attribute values not being HTML escaped (global attribute values are unaffected)
  • the string delimiter not being represented correctly when using encodings other than string (it would contain unnecessary backslash's in its byte representation)
  • certain syntax combinations wouldn't get expanded to interpolation correctly, and cause an infinite loop during compilation

Read more here: Release 0.11.0 · RandomHashTags/swift-htmlkit · GitHub

I would also like to thank @andrebraga for reporting issues! All of them got resolved in this release! (#3, #4, #5, #6)

Note that PointFree uses a new approach to HTML on their website. I extracted that out into coenttb/swift-html (GitHub - coenttb/swift-html: A Swift DSL for type-safe HTML & CSS, integrating swift-css and pointfree-html.). Please feel free to take a look there and see if there is anything you can use (MIT License). I also use coenttb/swift-css (GitHub - coenttb/swift-css: A Swift DSL for type-safe CSS.) for easy CSS styling.

The raw, updated PointFree code is in GitHub - coenttb/pointfree-html: A Swift DSL for type-safe HTML forked from pointfreeco/swift-html and updated to the version on pointfreeco/pointfreeco..

For example of how I use these packages, see GitHub - coenttb/coenttb-com-server: The source code for coenttb.com, written entirely in Swift in the style of PointFree and powered by Vapor & coenttb-web..

I'll definitely take a look around, thanks for sharing!