Swift + HTMX = A New Hope

Hello there, lovely swift community!

If you are anything like me, you love swift and strongly dislike javascript (like any reasonable person ; )

However, interactive web apps are essentially not possible without javascript - and more often then not you end up building and bundling fat client-side javascript codebases.

SwiftWasm and the inspiring ecosystem emerging around it can be a way out, but for my taste it is not quite ready for load-bearing prime time. Also, not every web app needs "fat" client-side logic.

So, what to do?

When I first heard about HTMX I did not think much of it. As it kept popping up I started to realize that it can be a nice way out:

  • keeps your application code on the server side (in beautiful swift)
  • achieves SPA-like interactivity
  • reduces the need for client-side code A LOT
  • no all-devouring javascript frontend library required

I am on a mission to make that work well. I think I am very close.

Check out the examples (both with auto-reload on save):
Hummingbird example app
Vapor example app

Let me know what you think!

17 Likes

This looks great, I am wondering, could Svelte and SvelteKit fit somewhere in this puzzle? Or would that not make sense? I am only a beginner to web technologies, but my understanding is Svelte is rather similar to SwiftUI and since its actually a compiler can do things other javascript frameworks cannot, making it an ideal web technology to use.
Would it make sense to build a transpiler from SwiftUI to Svelte like what Skip has done to transpile SwiftUI to Kotlin Jetpack Compose? I guess it's a very different approach? https://skip.tools

My general approach to software is: anything can be done if you just want it hard enough ; )

But in this case, the goal (at least mine) is to rely on the least amount of javascript as possible.

If you want to use svelte/sveltekit (and there is nothing wrong with that), I think you'll have an easier time just staying in the javascript ecosystem. "transpiling" swift to svelty-javascript feels way to complicated for the possible benefits it might bring.

To me, the more interesting way forward for "normal" web apps is:

  • stick to the basics with HTML, CSS, HTTP
  • do as much as you can on the server (in a proper language ; )
  • if you need client-side state, try to isolate it and keep it simple (htmx, web components, alpine, and similar can help a lot there)
  • if you need more sophisticated client-side state, contribute to SwiftWasm and help making it a viable option for fat-client web apps

To the last point, just look at Tokamak or the Embedded Wasm example by the wonderful @Max_Desiatov:
SwiftWasm client apps are definitely possible and basically already here. So my advice/wish would be: let's not spend more time on javascript, but instead give some love (and hard work) to polishing up SwiftWasm frameworks.

I have it on my list to will an embedded-wasm-compatible reactive client-side UI library based on elementary into existence, but I am a bit short on play time at the moment. If anyone wants to collaborate, please reach out!

3 Likes

OK this makes a lot of sense! Thank you, I was hoping for a high level vision for the future to try and understand this better, and thats what you've outlined here.

I think this all sounds great, I will be watching your elementary library for sure.

One question, where would you recommend hosting the swift server component for your simple web app? Like with Svelte, I would just use Vercel or Netlify, what would you recommend? I know Swift.clouds works on fastly via swift wasm, but it seems going native with something like aws lambda would be better, but I'm not sure if aws lambda is really built for serving something like this.

Not so sure about "vision", more like "random dude on the internet has opinions" ; ) But you are welcome.

I don't really have an opinion on this, as I mostly deal with on-premises/self-hosted environments. But anywhere should be fine, really. The last Server Side Meetup had a session about this.

The only general advise I would have is: stay clear of too much vendor-lock-in and stick to the open-source basics: linux, docker containers, postgres/mongo or similar, redis or similar. those fancy fully integrated magic cloud-something-something services might be nice to start, but they lock you in pretty hard.

1 Like

haha, well you are a random dude who knows more than me, so I'm thankful to you and this community for that.

I hear you, I don't yet have those skills so the magic buttons are still appealing to me. That being said, I also have experience with lock in and its perils, so I've come to really like Supabase. I use it for all my server needs, and since it's open source built around postgres, I could spin it off if I needed to. The missing piece has been around front end web deployment and architecture to go along with my mobile apps, so this gives me lots to think about. On another tangent, getting support for Swift within Supabase functions (built on deno deploy) would be really cool, but I understand it's a javascript world.

I think wasm as a technology still some times away. AFAIK it can't do DOM manipulation.

Currently swift on server focus has been to serve JSON for iOS apps. As it would be easier to share the same language for building App and the API.

I think swift has the capability to compete with MVC web frameworks like Django and Ruby on Rails. I think it makes more sense focus on improvement on web side of features in swift on server over Wasm.

Elementary looks promising as type safe templating language.

2 Likes

just a quick FYI update:

I tagged a elementary-htmx 0.2.0 today that includes SSE extensions.

this makes it very easy to live-update parts or your web UI via dead simple SSE streams, like so:

// in your html
div(.hx.ext(.sse), .sse.connect("/time")) {
    p(.sse.swap("time")) { "Server Time:" }
}

// in your route handler
app.get("time") { _ -> Response in
    Response(
        status: .ok,
        headers: ["Content-Type": "text/event-stream"],
        body: .init(managedAsyncStream: { writer in
            while true {
                try await writer.writeBuffer(.init(string: "event: time\ndata: Server Time: \(Date.now)\n\n"))
                try await Task.sleep(for: .seconds(1))
            }
        })
    )
}

the example may not be that impressive, but I am still enamoured by how little it takes with htmx to build robust and efficient(-ish) live-updating web apps - all without touching javascript. it is beautiful.

2 Likes