go-app
go-app copied to clipboard
Support for //go: embed
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.
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!
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),
}
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
}
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 .
Will the new version support this?
Any progress on this?
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.
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