GithubHelp home page GithubHelp logo

swiftwebui / swiftwebui Goto Github PK

View Code? Open in Web Editor NEW
4.1K 4.1K 148.0 220 KB

A demo implementation of SwiftUI for the Web

Home Page: http://www.alwaysrightinstitute.com/swiftwebui/

License: Apache License 2.0

Swift 99.53% Makefile 0.47%
swift-library swift5 swiftui swiftui-example swiftwebui

swiftwebui's Introduction

SwiftWebUI

Swift5.1 macOS tuxOS Travis

More details can be found on the related blog post at the Always Right Institute.

At WWDC 2019 Apple announced SwiftUI. A single "cross platform", "declarative" framework used to build tvOS, macOS, watchOS and iOS UIs. SwiftWebUI is bringing that to the Web βœ”οΈ

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

SwiftWebUI

So what exactly is SwiftWebUI? It allows you to write SwiftUI Views which display in a web browser:

import SwiftWebUI

struct MainPage: View {
  @State var counter = 0
  
  func countUp() { 
    counter += 1 
  }
  
  var body: some View {
    VStack {
      Text("πŸ₯‘πŸž #\(counter)")
        .padding(.all)
        .background(.green, cornerRadius: 12)
        .foregroundColor(.white)
        .onTapGesture(self.countUp)
    }
  }
}

Results in:

Unlike some other efforts this doesn't just render SwiftUI Views as HTML. It also sets up a connection between the browser and the code hosted in the Swift server, allowing for interaction - buttons, pickers, steppers, lists, navigation, you get it all!

In other words: SwiftWebUI is an implementation of (many but not all parts of) the SwiftUI API for the browser.

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

Requirements

On a Mac macOS 10.15 or later is required.

tuxOS

SwiftWebUI now runs on Linux using OpenCombine (also works without that, but then some things don't work, e.g. NavigationView).

Swift 5.2 or later is required. We also provide a Docker image containing a 5.1 snapshot over here: helje5/swift.

SwiftWebUI Hello World

To setup a SwiftWebUI project, create a "macOS tool project" in Xcode 11, then use the new SwiftPM integration and add https://github.com/SwiftWebUI/SwiftWebUI as a dependency.

Open the main.swift file and replace it's content with:

import SwiftWebUI

SwiftWebUI.serve(Text("Holy Cow!"))

Compile and run the app in Xcode, open Safari and hit http://localhost:1337/:

πŸ₯‘πŸž AvocadoToast

A small SwiftWebUI sample based on the SwiftUI Essentials "Avocado Toast App". Find it over here: AvocadoToast.

Who

Brought to you by Helge Heß / ZeeZide. We like feedback, GitHub stars, cool contract work, presumably any form of praise you can think of.

swiftwebui's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftwebui's Issues

Add support for multiple "windows"

The NIOHostingSession could easily track multiple virtual DOMs, one for each browser window.

If we would do proper WOContext's, that could work as a side effect. A WOContext is essentially a "snapshot" of a UI state. The sequence goes like "originating WOContext" => process event => "new WOContext".

Add support for Shapes and Clip pathes (CSS/SVG)

There is already some infrastructure for shapes (the types are defined), but they don't actually work yet :-)

It is probably quite possible (and maybe even easy for a CSS pro) to accomplish them using CSS and/or SVG. I already successfully circle-clipped a path as shown in the landmark demo. Those kind of things would be really cool!

Implement ZStack View

Alongside HStack/VStack the ZStack would be quite useful.

This doesn't sounds very hard, seems like this needs an outer div w/ position: relative and an inner wrapper div w/ position: absolute and a margin to position the elements.

The thing I could could quickly figure out is how the outer div could determine its size (yes, you can set one explicitly, but it would be nice if it could be max(allChildren.max) by default.

Do we need a SwiftStaticWebUI?

Inspired by John Sundell working on a nice HTML static site renderer, but using his own DSL.

In the spirit of what StaticCMS does for generating static sites using WebObjects/GETobjects templates, we could do a SwiftStaticWebUI which does the same using SwiftUI "Views".

Idea dump:

This is quite different to what SwiftWebUI itself does (and would best be done as a separate project).
Essentially all the diffing and state handling goes away. This would essentially not need Bindings (or Combine) anymore, it would be a one-way flow "swift views" => HTML page.
Targets of "actions" would work very different, they would need to resolve to statically representable links.

Just like in StaticCMS I imagine that we would need an "OFS" (Object File System), essentially a way to load a file system hierarchy into Swift values, which then get rendered by SwiftUI Views.
Imagine a filesystem folder containing .md files, they would load into say "Markdown" objects. Then you'd have SwiftUI Views like:

struct ArticleView: View {
  let article: Markdown
  var body: some View { ... }
}

which could turn them into HTML. The OFS in GETobjects also decouples the objects from the actual store, e.g. it could also be a database vending the markdown objects, but still being rendered using the exact same Views.

Then there would need to be entry points which trigger the html website rendering traversal, something like:

SwiftStaticWebUI.expose([
  "/": MainPage(),
  "/about": AboutPage()
])

Or maybe using some enum instead of strings.

NavigationLinks could also still work:

NavigationLink(destination: AboutPage()) {
  some anchor
}

This might need some back-mapping of the Views to the URLs. Since the Views can also be parameterised (e.g. as the body of a ForEach):

struct CounterPage: View {
  let counter : Int
...
}

... we need a away to serialise those into the URL (like /counters/1.html). One way to do this could be Codable. Which would automagically work for many basic types.

Not sure whether it's worth it, but it would at least allow me to replace my own sites driven by StaticCMS with something non-Java, more up to date :-)

Better Form support (CSS only)

The current Form View doesn't really do much. It is just a different container w/ a stretch alignment.

This could be enhanced a low, either by just repositioning content using CSS on the client side or by rewriting the form child trees on the server.

It would be cool to accomplish Form looks which mirror/use what SemanticUI does: https://semantic-ui.com/collections/form.html .

Add `IDView`

This might be the same or very similar to our SwitchView.

Finish `.onReceive` / `SubscriptionView`

This "just" needs to be finished, boilerplate is already available in Modifiers.

Note that @BindableObject itself works, it is "just" the modifier for attaching to objects as part of the View hierarchy which doesn't.

Hook up NIO WebSockets to provide realtime events

We currently use AJAX to connect the browser to the server. Using WebSockets has multiple advantages:

  • guaranteed event ordering (AJAX requests can arrive out of sync)
  • server side DOM updates
  • session timeout indicator

It would also make a chat client demo trivial.

Adding WebSockets is actually really easy because events are already sent as JSON. We just need the client and server side shims. All this is already tried out in swift-nio-irc-webclient and just needs to be ported over.

How to publish the binary product to a hosting service?

Hi,

I am really impressed with the framework! I tested some syntaxes and they work flawlessly.

However I am wondering if I can really use for publishing my personal website this way...

How can I host the resulting SwiftWebUI binary to e.g. WordPress or AWS?

I am asking this because I see that Wordpress requires HTML files (which I could not find inside the project) and AWS offers no MacOS instances (??)

Get rid of TreeStateContext / StateHolder's

TreeStateContext as a state holder is kinda superfluous and mostly complicates things. Since we have the old tree anyways (which also happens to carry instances of the view structs), we can as well use that to store the state (which we even do already for closures).

The original idea behind the state context was to have a single, defined, location for persistent state. Instead of having to keep the whole rendering tree around.

The way this would work is that we could get rid of the state holders. The view structs within the DynamicElementNode's themselves become the state holders. And when we create new instances of the same view in a new render tree, we need to fill in the view from there.
It's a little bit of fiddling to get this right, but it would probably be closer to what the original is doing.

EnvironmentObject EXC_BAD_ACCESS

EnvironmentObject value passed in view hierarchy changed on server callback crashes serwer.

error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated

error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0xfffffffffffffffd).

Inside

guard let box = slot.pointee.value as? StateValueBox else {
        fatalError(
          "@EnvironmentObject value is pointing to a different box type!")
      }

To reproduce fetch changes from tag #52
https://github.com/shial4/SWUI-NextUp/tree/%2352

  1. build & run.
  2. Select sign in tab
  3. Enter credentials ([email protected], test123456)

Click continue -> nothing will happen at first click (no clue why) wait like 5 seconds and click it second time. View will change and serwer will crash.

Add URL routing

Would be nice to attach views to URLs and maybe even hop between them instead of invoking closures using magic URLs.

Might give us history and other nice things, might require persistent WOContext state to avoid unwanted side effects.

Reference Swift WebUI

I want to write a SwiftKitUI using the system's UIKit and AppKit support for the low version. But I don't know how to write it. I don't know if I can read it according to the source code of SwiftWebUI.

Rework ForEach to not keep instantiated nodes

Right now ForEach is a real loop and actually N children. This is not what WORepetition does and it shouldn't be necessary for ForEach either.

Instead when looping over the contents, the tree should only consider and keep the currently active node and persist all item specific data in the tree context (that would also imply moving more state out of the tree, into the TreeStateContext).

This would be a pretty big rewrite, but maybe it should be done eventually in a v2.

Replace(?) "Traits" w/ proper `PreferenceKey`s

I've only seen them mention the mechanism once in the WWDC sessions, but "preferences" are the way to bubble up values in the view hierarchy (essentially the reverse to the environment).

Presumably it's the same I do w/ something I call "traits" in SwiftWebUI, e.g. this is used to bubble up tags to the list view or picker. Though maybe "traits" is just yet another thing :-)

Anyways, would be nice to know more about preferences and support them.

This thing by @anandabits is using some custom preference:
https://github.com/anandabits/SwiftUI-Processing/blob/e1e6a3e8b33c81434172153b3d754e4872a1a36a/Processing/CircleOfDots.swift

Export to static web page

Hi,

I was thinking if we could achieve something like static web page result.
As a product generated after build/run in the Xcode. What do you think?

If you think it is possible I am more than happy to help :)
Would need your help to direct me where to look, focus at.

Cheers,
Shial

Drop `TreeBuildingView` protocol

The TreeBuildingView should be dropped altogether.

There have still been issues with the compiler not picking the proper static HTMLTreeBuilder functions, but those are probably bugs in the signatures, or the types not being passed through properly.

The way to approach this is to completely drop the protocol. If it works, all is great :-) For buggy cases, this hits the builder function which takes View. Debug from there.

Potential issues with `Binding` creation

Hi @helje5 I just re-watched this WWDC talk: https://developer.apple.com/videos/play/wwdc2019/226/

At around 33:00 the speaker says that Binding does not capture references.

I tested it with BindableObject's subscript to verify the behavior.

public struct _Binding<Value> {
  private let _getter: () -> Value
  private let _setter: (Value) -> Void
  
  public var wrappedValue: Value {
    get {
      return _getter()
    }
    nonmutating set {
      _setter(newValue)
    }
  }
  
  public init(
    getValue getter: @escaping () -> Value,
    setValue setter: @escaping (Value) -> Void
  ) {
    _getter = getter
    _setter = setter
  }
}

class VM: BindableObject {
  let willChange = PassthroughSubject<Void, Never>()
  var value = ""
  
  public func getBinding<Subject>(
    keyPath path: ReferenceWritableKeyPath<VM, Subject>
  ) -> _Binding<Subject> {
    return _Binding(
      getValue: {
        self[keyPath: path]
      },
      setValue: { newValue in
        self[keyPath: path] = newValue
      }
    )
  }
}

var vm: VM = VM()
print(CFGetRetainCount(vm)) // 2
let biding_1: Binding<String> = vm[\.value]
print(CFGetRetainCount(vm)) // 3 - captures the reference once.
let biding_2 = vm.getBinding(keyPath: \.value)
print(CFGetRetainCount(vm)) // 5 🐞 - captures the reference twice.

You have similar code here:

https://github.com/swiftwebui/SwiftWebUI/blob/develop/Sources/SwiftWebUI/Properties/BindableObject.swift#L28

I suspect that they do use [unowned self] in one of the closures because the other closure is already known to capture the reference and it's not needed to do it twice. I think in case of the custom _Binding type from the example I would put it into the getter closure because it should be destroyed at first (I assume the top to bottom deallocation order here).

Support onDidAppear etc

TBD how and when those would get invoked, but it would be useful, e.g. in tab containers.

Maybe those are the awake/sleep of WOComponent's?

Fixup builds for Xcode 11b3

Merged in the Linux stuff (which already has the propertyWrapper change), but e.g. Tinker doesn't build right now. Need to finish the porting.

No

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

image

No, make it production-ready πŸ˜‚πŸ˜‚πŸ˜‚πŸ˜‚

No seriously, I need this in my life after a decade of HTML/CSS/JS 😭 I hope this project brings new perspective to the ecosystem.

Cheers, and thank you for putting together this project.

Combine "layer visuals affecting" views

As mentioned in: #40

We currently have the issue that visual affecting views do not modify a single div, but result in multiple divs. So this:

      Text("πŸ₯‘πŸž")
        .padding(.all)
        .background(.green, cornerRadius: 12)
        .shadow(color: .lightGray, radius: 5, x: 2, y: 2)

Results in:
Screen Shot 2019-07-26 at 15 42 21

I think the solution is to have .background, .shadow and the likes return a single "Style patching tree node".

Important: Layout nodes like .padding or .frame are different! They need to result in new divs because the ordering matters (or maybe they can combine if the ordering would lead to the same result). The old:

      Text("πŸ₯‘πŸž")
        .padding(.all)
        .background(.green, cornerRadius: 12)

      Text("πŸ₯‘πŸž")
        .background(.green, cornerRadius: 12)
        .padding(.all)

No available targets are compatible with triple

hello,
so I want to try this library to see if I can make apps with it.
but when I try the steps in https://github.com/carson-katri/swiftwebui-scripts, I get error when running the command (make), and I get the following error :
cd testingAppweUI &&
swift build --triple wasm32-unknown-wasi &&
cp .build/debug/testingAppweUI ../dist/SwiftWASM.wasm &&
wasm-strip ../dist/SwiftWASM.wasm &&
gzip ../dist/SwiftWASM.wasm --best
Fetching https://github.com/carson-katri/SwiftWebUI
Fetching https://github.com/MaxDesiatov/Runtime
Fetching https://github.com/kateinoigakukun/JavaScriptKit
Cloning https://github.com/kateinoigakukun/JavaScriptKit
Resolving https://github.com/kateinoigakukun/JavaScriptKit at 1edcf70
Cloning https://github.com/MaxDesiatov/Runtime
Resolving https://github.com/MaxDesiatov/Runtime at wasi-build
Cloning https://github.com/carson-katri/SwiftWebUI
Resolving https://github.com/carson-katri/SwiftWebUI at develop
error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-wasi"'
error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-wasi"'
1 error generated.
1 error generated.
[0/7] Compiling _CJavaScriptKit dummy.c
make: *** [build] Error 1

thank you for making this

Scrollview doesn't always work (Flexbox)

This probably requires a review where our flexbox layouts leak.

Often "inner" scrollviews do not work and the browser viewport receives the scrollbar. That is probably because some divs we generate do not have proper vertical sizing assigned.

@State property change ends with error

I have a
@State var alert: (header: String, message: String)? = nil
changing it's value do not remove view from DOM. Further more the call ends with error:
[Error] Failed to load resource: the server responded with a status of 500 (Internal Server Error) (handle, line 0)

The value is changed inside:

.onTapGesture {
       self.signIn()
}

Add Codable TreeStateContext

If all boxed component values are Codable (or serialisable by other means), we could persist them and thereby decouple the state from a specific server. E.g. store in Redis.

See SwiftObjects WOSessionStore savePage etc.

Not sure it's worth it :-)

Sample Example Run Xcode Beta3 Run Error?

dyld: Symbol not found: _$s7Combine11SubscribersO4SinkCy_xq_GAA11CancellableAAMc
Referenced from: /Users/zhangxing/Library/Developer/Xcode/DerivedData/Tinker2-cxubagqiogijkfhczskwfdfwlmmb/Build/Products/Debug/Tinker2
Expected in: /System/Library/Frameworks/Combine.framework/Versions/A/Combine
in /Users/zhangxing/Library/Developer/Xcode/DerivedData/Tinker2-cxubagqiogijkfhczskwfdfwlmmb/Build/Products/Debug/Tinker2

Make it threadsafe

Some things are not threadsafe, the relevant sections should be marked properly in the code.

This is not a problem right now because we only spawn a NIO event loop with a single thread.

There is no reason MT wouldn't work, not sure whether it's worth the effort. A worker thread like in SwiftObjects might be.

Expire sessions

Sessions currently live forever :-) This "just" needs a proper timer.

Can we do "growing" Spacers (Colors etc) using just Flexbox?

Currently spacers are hacked-in in the HTML generation phase because I didn't know how to do it properly in flexbox :-)

In SwiftUI, as soon as you put a Spacer inside a layout container, the layout container asks for the full available space, while in Flexbox the divs stick to their intrinsic size (even if the spacer div has a flex-grow: 99 the outer boxes do not inherit that desire to grow).
I bet this is also possible w/ flexbox :-)

Currently this is implemented by traversing the hierarchy for spacer like nodes, and if such are found, the containers add a width/height: 100% to their styles.

Rework differ to only rebuild subtrees of invalidated dynamic views

Currently we build a completely new shadow DOM for every event, then diff it with the last one.

We do actually track which dynamic views (and therefore subtrees) are invalid, the infrastructure to only rebuild changed subtrees is available already (and was on until recently).
However, that had to be disabled to properly support tree rewrites because rewrites can span dynamic views ("components").

Not quite sure what the solution is / how to track what parts need to rerender.

TODO: attach an example producing the issue w/ subtree rerendering.

Support more SF Symbol Mappings

Many SF symbol to Font Awesome mappings have to be handcoded. A review is necessary to match up SF symbols w/ Font Awesome as supported by SemanticUI.

Just plain boring work.

`Spacer` not same as `SwiftUI`

HStack {
                Text("\(index)")
                Spacer()
                Button(action: {
                    self.index += 1
                }) {
                    Text("Cilie Me")
                }
            }

Text and Button not in Web Content Left And Right .Now in all Left Offset

Drop all `!important` in CSS

What the title says. There are few !important in the CSS to hack-override SemanticUI styles. Those just need to be qualified more specific so they take preferences (e.g. by adding our own classes to the the style).

Linux port / NoCombine

To run things on Linux we only really need a tiny subset of Combine, and the types for that are already prepared in Misc/NoCombine.swift.

We just need a way to track the sinks attached to a PassthroughSubject.

P.S.: A Swift 5.1 container would be also required of course.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.