SwiftUI like Templating for Server Side Swift

Hi,

most people here have probably seen my SwiftWebUI project. Which comes with that big disclaimer:

Disclaimer : This is a toy project! Do not use for production. Use it to learn more about SwiftUI and its inner workings.

Which is still true :slight_smile: Yet I still get feedback from people actually wanting to use it.

So in my sparetime I've been working on something I call SwiftWebViews, which is designed to be actually useful. I didn't release it yet, and it is still a little early, but I wanted to get some feedback whether people even consider that interesting.

Why SwiftWebUI should not be used

But let's first discuss why you should not use SwiftWebUI. I think there are three key reasons:

  • it tries to replicate the SwiftUI layout system, which is not really possible
  • as-is it uses a lot of in-memory state on the server side
  • it is fundamentally wrong to build web apps like that

In short, it really tries to be the missing Web checkbox for "SwiftUI". Which is a futile attempt (it may become more interesting when we can run Swift on the actual client, e.g. via WASM).

SwiftWebViews

This one intentionally doesn't have "UI" in the name. It doesn't strive for replicating SwiftUI for the web. Instead the idea is to use SwiftUI concepts, primarily Views (render components), function builders and specifically the environment, for the web.
The core isn't tied to any specific framework (not even NIO), it just takes a stream protocol as its rendering target (it could in theory also have a request processing phase).

The way to write a template looks pretty much like SwiftUI, e.g.:

import HTMLWebViews

struct MyPage: WebView {
  var body: some WebView {
    HTML { // WebView's coming from the HTML module
      Head {
        Script(""" ...""")
      }
      Body  {
         ...
      }
      .class("page-wrapper")
      .id("root")
    }
  }
}

Those would be the primitives, but with the SwiftUI like View composition, you can also provide modules that build on top of that, e.g.:

import SemanticUIWebViews

struct MyPage: WebView {
  var body : some WebView {
    Page(title: "Blub") {
      TabbedView {
        Tab { .. }
        Tab { .. }
      }
    }
  }
}

There should also be XML generation primitives, and on top of that RSS etc. That's the templating part.

Environment & Integration w/ Frameworks

The real power is coming from the environment, which acts like a neat, extensible DI system. Again, the idea is that the WebViews module itself would be framework agnostic, and frameworks layer on top of that. The environment is a great way to accomplish/decouple that.

For example, to hook it up w/ plain NIO, and make the NIO HTTPRequest available to the pages and all their children you could do:

MyPage
  .environment(\.request, httpRequest) // or just `.request(httpRequest)`

struct MyPage: WebView {
  @Environment(\.request) var request

  var body: some WebView {
    Text("Request coming from: \(request.uri)")
    if request.uri.contains("/help") {
      MyHelpPage
    }
    else {
      MyRegularPage
    }
  }
}

SwiftWebViews doesn't need to know about NIO, but it still supports the infrastructure to inject NIO specific things (using custom EnvironmentKeys).

The same thing works for ORM frameworks, e.g. to inject a ZeeQL editing context, you can just pass it down:

let database = ... setup db environment ...
let page = MyUserListPage
  .dataSource(database.dataSourceForEntity("users"))
let ctx = WebViewContext(outputStream)
ctx.render(page)

There could also be framework specific property wrappers, e.g. one to extract form values like this:

struct MyPage: WebView {
  @FormValue("name") var name : String
  @FormValue("age")  var age  : Int
}

That property wrapper would rely on a framework specific environment object that carries the values.

What it doesn't have

Anything Combine like. E.g. ObservedObjects. Which make no sense for the generation of a "one off" document, either XML or HTML.

There is (intentionally) no diffing.

The SwiftUI layout system. On the web you should use CSS ...

No event handling (though that could be added, similar to the takeValues/invokeAction phases in WebObjects).

No SwiftUI List, NavigationView etc - those would be provided by specific frameworks.

Status

I already separated out and adjusted the View construction and Environment code from SwiftWebUI and started a simple HTML primitives module. But nothing fancy.
If there is interest, I could upload it for further discussion, but it is still very early.

Summary

As mentioned my main motivation for that post is to gather opinions on whether that makes sense, or is just stupid non-sense :wink: Would you want to use something like that? Is it of interest to the greater "sss community", i.e. would frameworks want to adopt and build on top of that?

Thanks!

21 Likes

Sounds intriguing!

This is somewhat related: GitHub - Macro-swift/MacroApp: A SwiftUI-like, declarative way to setup MacroExpress Endpoints, but for endpoint definition instead of content:

@main
struct HelloWorld: App {
  
  var body: some Endpoints {
    Group {
      Use(logger("dev"), bodyParser.urlencoded())
          
      Route("/admin") {
        Get("/view") { req, res, _ in res.render("admin-index.html") }
        Render("help", template: "help")
      }
      
      Get { req, res, next in
        res.render("index.html")
      }
    }
  }
}
4 Likes

I would be very interested to see where this goes.

BTW: @main doesn't actually work in SPM projects yet :slight_smile:, one has to manually call HelloWorld.main().

On the pro site, the Group isn't necessary anymore in Swift 5.3, which makes it even nicer (because body is already marked as an EndpointsBuilder in the protocol)

Also kinda related: I've released SwiftBlocksUI which is "SwiftUI style components for Slack apps" (allows you to write Slack dialogs and interactive message in a SwiftUI like DSL). Also uses MacroApp (the declarative endpoints).

SwiftBlocksUI has a very specific backend for the related Slack JSON, but is much closer to what I envision for the "SwiftWebViews" described above.
Need to find the time to bring the latter into some shape ...

I'm not 100% sure yet what HTML(/XML) primitives it should provide. E.g. is Body { ... }.id("root") better or Body(id: "root") { ... }.
If someone has suggestions or comments on the actual DSL, I'd love to hear about them!

Another thing I didn't really figure out is how to make the stuff type-safe in a reasonable way. E.g. so that you can't embed an HTML p in another p etc. (I think John Sundell does this pretty well in Plot).
You'd essentially have to have one protocol per content restriction and then replicate all the common structs (all the Tuples, ForEach, Group etc). It's a little unwieldy, but maybe the way to go. Maybe someone has other ideas here.