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!

20 Likes

Sounds intriguing!

This is somewhat related: https://github.com/Macro-swift/MacroApp, 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")
      }
    }
  }
}
2 Likes
Terms of Service

Privacy Policy

Cookie Policy