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

Support for //go: embed #501

Closed
andrewrynhard opened this issue Feb 18, 2021 · 9 comments
Closed

Support for //go: embed #501

andrewrynhard opened this issue Feb 18, 2021 · 9 comments

Comments

@andrewrynhard
Copy link

I would like to be able to deliver a single binary with all the required assets baked into it. The new embed package in go 1.16 could make this easy. I tried embedding app.wasm, but app wants to use a local directory.

@maxence-charriere
Copy link
Owner

There is cool stuff here. I already see a couple of things that would make building the package easier.
I'll definitely take a look at this and see how to take advantage of it!

@dabbertorres
Copy link

dabbertorres commented Jul 6, 2021

I was able to do this by implementing app.ResourceProvider and http.Handler, similar to app.LocalDir.

type resourcesFS struct {
	http.Handler
}

func newResourcesFS(fsys fs.FS) app.ResourceProvider {
	return resourcesFS{
		Handler: http.FileServer(http.FS(fsys)),
	}
}

func (resourcesFS) Package() string { return "" }
func (resourcesFS) Static() string  { return "" }
func (resourcesFS) AppWASM() string { return "/web/app.wasm" }

And then in two separate files (to avoid embedding resources again in app.wasm):

embedded_resources.go:

// +build !wasm,!js

package main

import (
	"embed"
)

//go:embed web
var web embed.FS

and

empty_resources.go:

// +build wasm,js

package main

import (
	"io/fs"
)

var web fs.FS

Then when setting up your app.Handler:

handler := &app.Handler{
	Name:      "App",
	// ...
	Resources:    newResourcesFS(web),
}

@justinfx
Copy link

I was having a play with this embedded solution and found that this modified approach only requires one file instead of two:

main.go

package main
// ...

var web fs.FS

type resourcesFS struct {
	http.Handler
}

func newResourcesFS(fsys fs.FS) app.ResourceProvider {
	return resourcesFS{
		Handler: http.FileServer(http.FS(fsys)),
	}
}

func (resourcesFS) Package() string { return "" }
func (resourcesFS) Static() string  { return "" }
func (resourcesFS) AppWASM() string { return "/web/app.wasm" }

And then only a single conditional embed.go for the server:

embed.go

// +build !wasm,!js

package main

import (
	"embed"
)

//go:embed web
var embeddedWeb embed.FS

func init() {
	web = embeddedWeb
}

@justinfx
Copy link

And one more variation that might even be better, since it allows for making the embedding a build constraint:

main.go

package main

//...

var (
	resources app.ResourceProvider
)

func main() {
    // ...
    handler := &app.Handler{
        // ...
		Resources: resources,
    }
}

embed.go
Note the added "embed" build constraint

// +build !wasm,!js,embed

package main

import (
	"embed"
	"net/http"
)

//go:embed web
var embeddedWeb embed.FS

func init() {
	resources = resourcesFS{
		Handler: http.FileServer(http.FS(embeddedWeb)),
	}
}

type resourcesFS struct {
	http.Handler
}

func (resourcesFS) Package() string { return "" }
func (resourcesFS) Static() string  { return "" }
func (resourcesFS) AppWASM() string { return "/web/app.wasm" }
# without embedding
$ go build -o ./server .

# with embedding
$ go build -o ./server -tags embed .

@ystyle
Copy link

ystyle commented Oct 9, 2021

Will the new version support this?

@qmilangowin
Copy link

Any progress on this?

@oderwat
Copy link
Contributor

oderwat commented Feb 11, 2024

As we deploy the apps with docker there is usually no need to embed the "web" folder for us, but I was interested in this anyway. I think it is fairly simple to embed the web folder for v9 (and v10) using:

import (
	"embed"
	"net/http"

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

//go:embed web
var web embed.FS

var _ app.ResourceResolver = (*embeddedResourceResolver)(nil)

func ResourceFS(web embed.FS) app.ResourceResolver {
	return embeddedResourceResolver{
		Handler: http.FileServer(http.FS(web)),
	}
}

type embeddedResourceResolver struct {
	http.Handler
}

func (r embeddedResourceResolver) Resolve(location string) string {
	if location == "" {
		return "/"
	}
	return location
}

func main() {
    // ...
    handler := &app.Handler{
        // ...
	Resources: ResourceFS(web),
    }
}

P.S.: You want to separate WASM and server builds to not include the backend code in the frontend. So this should go in the backend part.

@mlctrez
Copy link
Contributor

mlctrez commented Feb 11, 2024

For another example, here's a project I created that embeds all static content ( including the wasm ) in the resulting server binary. It follows the best practice @oderwat mentioned to not include backend code in the wasm and also has some other useful features when starting a new go-app project.

https://github.com/mlctrez/goappnew

@maxence-charriere
Copy link
Owner

Should be doable by implementing ResourceResolver that satisfy the http.Handler interface.

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

No branches or pull requests

8 participants