wailsapp / wails

Create beautiful applications using Go
https://wails.io
MIT License
25.25k stars 1.21k forks source link

Adding support for go-app #2282

Open dzonerzy opened 1 year ago

dzonerzy commented 1 year ago

Is your feature request related to a problem? Please describe.

Wails is a great product which allow building richful UI via web tech, I was thinking of a way to allow someone who's not too familiar with web technologies to build such rich UI.

Describe the solution you'd like

I think supporting https://github.com/maxence-charriere/go-app could be a plus, it allow build PWA using pure go declarative syntax

Describe alternatives you've considered

No response

Additional context

As long as I know go-app make use of wasm to build UI, being wasm itself a web technology I don't think should be too hard to implement, instead of running an assets server wails could give users the option to run their own defined server which would display go-app application.

leaanthony commented 1 year ago

If you're happy to spend time making a PoC then I'm happy to spend time looking at how we can integrate it

dzonerzy commented 1 year ago

Also go-app support out of the box static site generation, so building a template could be actually easier, imo the best way is still adding full support, I'll try to make a poc and ping you back.

dzonerzy commented 1 year ago

I'm stuck on an issue which seems like I can't figure out how to solve, when building go-app application a wasm file is created (creation of this file could be handled by npm, right now i'm using go generate), anyway mimetype for wasm seems to be application/vnd.microsoft.portable-executable I tried to create the following middleware but content-type won't change:

/*
 * go-app-poc
 *  To use with linux change the following lines:
 *  //go:generate GOOS=js GOARCH=wasm go build -o frontend/src/web/app.wasm -ldflags=-w -ldflags=-s ./app
 */

//go:generate cmd /c set GOOS=js
//go:generate cmd /c set GOARCH=wasm
//go:generate go build -o frontend/src/web/app.wasm -ldflags=-w -ldflags=-s ./app
//go:generate go run ./app

package main

import (
    "embed"
    "net/http"

    "github.com/wailsapp/wails/v2"
    "github.com/wailsapp/wails/v2/pkg/options"
    "github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

//go:embed all:frontend/src
var assets embed.FS

func mimeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // if url is /web/app.wasm, set the mime type to application/wasm
        if r.URL.Path == "/web/app.wasm" {
            w.Header().Set("Content-Type", "application/wasm")
        }
        next.ServeHTTP(w, r)
    })
}

func main() {

    // Create an instance of the app structure
    WailsApp := NewApp()

    // Create application with options
    err := wails.Run(&options.App{
        Title:  "go-app-poc",
        Width:  1024,
        Height: 768,
        AssetServer: &assetserver.Options{
            Assets: assets,
            Middleware: assetserver.ChainMiddleware(
                mimeMiddleware,
            ),
        },
        BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
        OnStartup:        WailsApp.startup,
        Bind: []interface{}{
            WailsApp,
        },
    })

    if err != nil {
        println("Error:", err.Error())
    }
}

Below the response headers

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 7604224
Content-Type: application/vnd.microsoft.portable-executable
Last-Modified: Mon, 09 Jan 2023 12:54:14 GMT
Date: Mon, 09 Jan 2023 13:01:09 GMT

Note that go-app check app.wasm mimetype before loading it

dzonerzy commented 1 year ago

Got it working, it was my issue the wasm wasn't generated correctly, below the poc repo:

https://github.com/dzonerzy/go-app-poc

Right now I use go generate to build wasm on the fly but that could be done by the watcher itself when rebuild the application. Also I'm sure there are better way to interop with wails runtime , in the poc I call the greet function.

Let me know what you think and I'll be more than happy to help you with that.

Regards, dzonerzy

jurjevic commented 1 year ago

Any progress on this topic?

I like the idea to use Go as the only programming language.

dzonerzy commented 9 months ago

@leaanthony do you need more info on the topic? I can try to implement that by myself if you prefer.

leaanthony commented 9 months ago

Hey 👋 that sounds great! This flew under the radar a bit.

dzonerzy commented 9 months ago

Almost finished the new template. It needs some polishing, and I must find a better way to handle bindings, but it's still a starting point; I already integrated it inside Wails cli.

image

Right now I use some hack to make it works fine with Vite server for dev purposes, but I'm sure this can be done way better.

//go:generate go run .
//go:generate go run generate_wasm.go
//go:generate npx tailwindcss-cli@latest build -i main.css -o style.css

package main

import "github.com/maxence-charriere/go-app/v9/pkg/app"

type hello struct {
    app.Compo

    name  string
    greet string
}

func (h *hello) Render() app.UI {
    return app.Div().ID("App").Body(
        app.Img().Src("/images/logo-universal.png").Alt("logo").ID("logo"),
        app.Div().ID("resut").Class("result").Body(
            app.If(h.name != "", app.Text(h.greet)),
        ),
        app.Div().ID("input").Class("input-box").Body(
            app.Input().ID("name").Class("input text-black").AutoComplete(false).
                Name("input").Type("text").OnChange(h.ValueTo(&h.name)),
            app.Button().Class("btn").OnClick(h.Greet).Text("Greete"),
        ),
    )
}

func (h *hello) Greet(ctx app.Context, e app.Event) {
    res := app.Window().Get("go").Get("main").Get("App").Call("Greet", h.name)
    res.Then(func(v app.Value) {
        h.greet = v.String()
        h.Update()
    })
}

func main() {
    app.Route("/", &hello{})

    app.RunWhenOnBrowser()

    h := &app.Handler{
        Title: "Hello",
        Styles: []string{
            "style.css?direct",
            "app.css?direct",
        },
        Description: "A progressive web app written in Go.",
    }

    app.GenerateStaticWebsite(".", h)
}

Forgive the extra 'e' in Greet !

leaanthony commented 9 months ago

This is incredible! 👏 If the frontend is compiled wasm, is there a need for vite?

dzonerzy commented 9 months ago

I added vite with a custom configuration and some plugins, in that way it's possible to have an immediate feedback when something change, right now when a go file is modified there's a vite hook which will generate the static files and and will build the wasm.

leaanthony commented 9 months ago

Ah I see. Thanks for the clarification

dzonerzy commented 9 months ago

What do you think is the best way to include such a template? Do you prefer having it as an external template or built-in inside the cli? I see many people have external repos with their templates. Also, regarding the binding at the moment, the wails binding is unused since the vite server is only building sources from frontend/src/. Still, I would like to know if it is possible to generate golang bindings together with js + ts bindings. This way, adding a go.mod files inside the frontend directory is enough to make them importable from go-app.

leaanthony commented 9 months ago

External template for now as the maintainers aren't across go-app (yet!). Regarding bindings, I'm not 💯 on what you mean. It's not possible to call any new Go bindings without recompiling the app.

dzonerzy commented 9 months ago

I think it's way better if you could look at the repo https://github.com/dzonerzy/wails-go-app-v2 right now, there are a few things I don't like. Maybe you could suggest better ways to handle such cases:

1 - The frontend distribution folder is and must be outside the frontend folder 2 - There's no direct way to use the wails-generated binding in Go since Vite removes them during the bundling operation due to tree shake (they are used in Go, not in JS or TS) 3 - Vite uses a lot of custom plugins to copy needed files for distribution. Is there a better way to handle and bundle WASM files?

Now let me explain why I was forced to make some of the above choices:

1 - Golang can't embed a file/folder residing in a different module (since the frontend folder is a go module itself due to the go-app) 2 - I haven't found a solution to this issue. I was also thinking about making wails-generating go-app binding in Golang; that could be handy since they are already inside the frontend module so that the go-app could import them. 3 - I could not find a better solution than using a custom site hook to copy static files (WASM, js) to the distribution folder.

Any feedback is appreciated, Thanks!