Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More control over frontend #2007

Closed
mholt opened this issue Oct 23, 2022 · 12 comments · Fixed by #2016
Closed

More control over frontend #2007

mholt opened this issue Oct 23, 2022 · 12 comments · Fixed by #2016
Labels
Enhancement New feature or request

Comments

@mholt
Copy link
Contributor

mholt commented Oct 23, 2022

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

All of my frontend pages (on an existing project) need server-side template processing (I use Go's text/template and html/template packages), including index.html. Thus I need to be able to render them in my own ServeHTTP, not a built-in handler implementation.

Describe the solution you'd like

I'm not sure the best solution as I don't know enough about the internals, but maybe a way that my own http.Handler can serve all the assets instead of as a "fallback" would be enough. Then I could use index.html and wouldn't need to rewrite things (but could if I wanted to).

Describe alternatives you've considered

My first attempt was to set Assets to nil in the app options and set an AssetsHandler to my own HTTP handler, but the built-in handler still magically takes over. Maybe it finds the embedded FS on its own? Anyway, I was thinking I could skirt around the built-in handler if the index.html file 404'ed, thus calling my AssetsHandler, as long as I make sure every link goes to a page that doesn't exist. I just have to rewrite the URL server-side, internally. No problem.

But I'm having a bit of trouble with this, since index.html is a required asset:

    Could not resolve entry module (index.html).
    error during build:
    Error: Could not resolve entry module (index.html).
        at error (...project/frontend/node_modules/rollup/dist/shared/rollup.js:198:30)
        at ModuleLoader.loadEntryModule (...project/frontend/node_modules/rollup/dist/shared/rollup.js:22483:20)
        at async Promise.all (index 0)

This means I can't rewrite requests to / or /index.html to, say _index.html or myindex.html because the program won't even build without a real index.html file. Which means I can't serve it using my own handler, and can't evaluate templates server-side.

Additional context

Thank you for Wails, it's quite awesome :D

@mholt mholt added the Enhancement New feature or request label Oct 23, 2022
@leaanthony
Copy link
Member

I haven't thought too hard about this, and I'm sure @stffabi would be able to provide a better response but I'm thinking we could abstract the current assets handler from the code and put it behind an interface. That way you can provide your own handler.

The one caveat, I guess, is how this would work in Dev mode. You'd have to forgo the use of Vite, but it sounds like that's the desired outcome.

@stffabi
Copy link
Collaborator

stffabi commented Oct 23, 2022

I'm not sure we really need another abstraction for that. Basically setting the Assets to nil, just completely drops the default Wails asset handling that goes to the assets. Since everything is implicitly not found, everything can be handled by the AssetsHandler.

I still think everything should be in place to support that, but since it's more of an advanced topic, it needs some changes in how your Wails project is setup compared to a default Wails project and comes with some drawbacks in the Dev mode experience

The following example (wails.json and main.go) gives one full control over the Assets and allows everything to be handled by the AssetsHandler.

wails.json

{
  "name": "demo",
  "outputfilename": "demo",
  "wailsjsdir": "./build/trash",
  "author": {
    "name": "stffabi",
    "email": "stffabi@users.noreply.github.com"
  }
}

main.go

package main

import (
	"net/http"

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

func main() {
	// Create an instance of the app structure
	app := NewApp()

	// Create application with options
	err := wails.Run(&options.App{
		Title:  "mholt",
		Width:  1024,
		Height: 768,
		AssetsHandler: http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				w.Write([]byte("Content of: " + r.RequestURI))
				w.WriteHeader(http.StatusOK)
			},
		),
		BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
		OnStartup:        app.startup,
		Bind: []interface{}{
			app,
		},
	})

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

As @leaanthony already mentioned the dev mode has limitations, since everything is now done by the AssetsHandler there's no Vite support anymore.

Regarding your issues @mholt

My first attempt was to set Assets to nil in the app options and set an AssetsHandler to my own HTTP handler, but the
built-in handler still magically takes over. Maybe it finds the embedded FS on its own?

I suspect it failed in your case during wails dev because there was still a frontend:dev:serverUrl in use. A frontend dev server takes the place of the assets during wails dev. So even when Assets is nil in wails dev, the frontend dev server is still asked if it could serve the files as a first chance handler.
This could be improved by either informing the user that the setup is inconsistent or we might just ignore the the frontend dev sever if Assets is nil.

But I'm having a bit of trouble with this, since index.html is a required asset:

Could not resolve entry module (index.html).
error during build:
Error: Could not resolve entry module (index.html).
at error (...project/frontend/node_modules/rollup/dist/shared/rollup.js:198:30)
at ModuleLoader.loadEntryModule (...project/frontend/node_modules/rollup/dist/shared/rollup.js:22483:20)
at async Promise.all (index 0)

This means I can't rewrite requests to / or /index.html to, say _index.html or myindex.html because the program won't even build without a real index.html file.

I would guess that comes from the frontend build process, and from the frontend framework that is used. That would be need to be changed there. In the demo above, one basically skips all frontend build process etc.

Improvements

I see the following places for improvements.

  • Inform the user if Assets are nil and a frontend dev sever is used in wails dev or ignore frontend dev server when assets are nil.
  • In the example above one doesn't need the wailsjsdir in wails.json. Maybe we could handle an undefined wailsjsdir in another way than an empty wailsjsdir. Undefined = use default, Empty = ignore.

@mholt
Copy link
Contributor Author

mholt commented Oct 23, 2022

I suspect it failed in your case during wails dev because there was still a frontend:dev:serverUrl in use.

It looks like it's set to "auto" -- would that do it?

I tried removing it and unfortunately still get the error.

I would guess that comes from the frontend build process, and from the frontend framework that is used. That would be need to be changed there. In the demo above, one basically skips all frontend build process etc.

It's vanilla JS... no frontend framework or build process. I'm boring like that.

Maybe exposing the frontend dev server (that serves its assets) as an http.Handler value could be useful, if we wanted to provide our own, then we could still invoke the internal logic when we need to.

@stffabi
Copy link
Collaborator

stffabi commented Oct 23, 2022

I would guess that comes from the frontend build process, and from the frontend framework that is used. That would be need to be changed there. In the demo above, one basically skips all frontend build process etc.

It's vanilla JS... no frontend framework or build process. I'm boring like that.

If you have initialised the project with wails init -t vanilla there's still Vite that gets started, which won't succeed if there's no index.html.

Did you try to update your wails.json according to my example above? Basically removing all those frontend:* stuff should do the trick.

Would you mind sharing your Wails.json and your main.go, maybe that would make it easier to directly helping you to setup the project?

Maybe exposing the frontend dev server (that serves its assets) as an http.Handler value could be useful, if we wanted to provide our own, then we could still invoke the internal logic when we need to.

I'm not sure if this would help in this case, since the error of the missing index.html happens before any of the user's code is run. This error happens due to starting Vite before starting the development binary.

Nevertheless we could however as I've already shortly mentioned on Slack, add an AssetsMiddleware in form of AssetsMiddleware func(http.Handler) http.Handler. So there you could hook into the HTTP chain and add your custom code before the Wails default handler. One can also skip the default Wails handler for specific resources.

This is pretty straight forward and should give plenty of flexibility for such cases. I've already a local branch with that support ready.

@mholt
Copy link
Contributor Author

mholt commented Oct 24, 2022

@stffabi Ahh, right, I forgot about Vite.

You are right, if I remove all the frontend: stuff from the wails.json and set Assets: nil, none of the build steps are performed and I can use my AssetsHandler for all pages. This does change my site root though, so I need to change the go:embed tag.

I also need to change import {...} from '../../wailsjs/go/application/App'; because apparently it fails to work without the .js extension now, so making it App.js fixes it.

Mostly little things.

But I can now evaluate templates server-side on my index.html file!

Upon saving a file:

Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs

Makes sense, so I add -assetdir ./application/frontend to wails dev and now reloads work, although more janky than with Vite which can smoothly reload style sheets without a complete page reload.


Thanks to your help I think I'm up and running. But it's not ideal.

This sounds great though:

add an AssetsMiddleware in form of AssetsMiddleware func(http.Handler) http.Handler. So there you could hook into the HTTP chain and add your custom code before the Wails default handler. One can also skip the default Wails handler for specific resources.

^ That'd probably be my favorite plan! :)

@leaanthony
Copy link
Member

Glad you got up and running in your custom setup. Obviously workarounds are just that. If we were to make a significant change like adding a middleware handler, based on your experience, what would that look like? Are there any more little things that would need to change or be considered? The last thing I want to do is hack in a solution, so let's consider this holistically and nail it! :-)

@stffabi
Copy link
Collaborator

stffabi commented Oct 24, 2022

Great you got it working. I have a branch ready with the middleware, which we could use as discussion base.
It doesn't affect much and from an api point of view there's not a lot needed apart from defining the middleware somewhere in the options.

@mholt
Copy link
Contributor Author

mholt commented Oct 24, 2022

@stffabi Oh perfect. I'd love to test a PR.

@leaanthony I think @stffabi's API would work: wrapping a handler in another handler. I just need a handler I can call if/when I want to.

@stffabi
Copy link
Collaborator

stffabi commented Oct 25, 2022

@mholt a draft PR #2016 is up for testing.

@mholt
Copy link
Contributor Author

mholt commented Oct 25, 2022

@stffabi Awesome! Checking it out now.

@mholt
Copy link
Contributor Author

mholt commented Nov 9, 2022

This has been working great for me for the last ~week, thank you!

@leaanthony
Copy link
Member

Will be doing a release tonight hopefully

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants