diff --git a/v2/cmd/wails/internal/commands/generate/template/base/main.go.tmpl b/v2/cmd/wails/internal/commands/generate/template/base/main.go.tmpl index 268a44348..d8e902027 100644 --- a/v2/cmd/wails/internal/commands/generate/template/base/main.go.tmpl +++ b/v2/cmd/wails/internal/commands/generate/template/base/main.go.tmpl @@ -7,6 +7,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/windows" ) @@ -36,7 +37,9 @@ func main() { StartHidden: false, HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255}, - Assets: assets, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Menu: nil, Logger: nil, LogLevel: logger.DEBUG, diff --git a/v2/cmd/wails/internal/commands/initialise/templates/generate/plain/main.go.tmpl b/v2/cmd/wails/internal/commands/initialise/templates/generate/plain/main.go.tmpl index 8f37a961a..433cfee4e 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/generate/plain/main.go.tmpl +++ b/v2/cmd/wails/internal/commands/initialise/templates/generate/plain/main.go.tmpl @@ -9,6 +9,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/windows" ) @@ -37,7 +38,9 @@ func main() { StartHidden: false, HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - Assets: assets, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Menu: nil, Logger: nil, LogLevel: logger.DEBUG, diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/vue/main.tmpl.go b/v2/cmd/wails/internal/commands/initialise/templates/templates/vue/main.tmpl.go index e0f197dee..e24782be3 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/templates/vue/main.tmpl.go +++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/vue/main.tmpl.go @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/internal/app/app_dev.go b/v2/internal/app/app_dev.go index d807ed9a3..35c02678f 100644 --- a/v2/internal/app/app_dev.go +++ b/v2/internal/app/app_dev.go @@ -13,6 +13,7 @@ import ( "path/filepath" "github.com/wailsapp/wails/v2/internal/binding" + "github.com/wailsapp/wails/v2/internal/frontend/assetserver" "github.com/wailsapp/wails/v2/internal/frontend/desktop" "github.com/wailsapp/wails/v2/internal/frontend/devserver" "github.com/wailsapp/wails/v2/internal/frontend/dispatcher" @@ -90,6 +91,14 @@ func CreateApp(appoptions *options.App) (*App, error) { } } + assetConfig := assetserver.BuildAssetServerConfig(appoptions) + + if assetConfig.Assets == nil && frontendDevServerURL != "" { + myLogger.Warning("No AssetServer.Assets has been defined but a frontend DevServer, the frontend DevServer will not be used.") + frontendDevServerURL = "" + assetdir = "" + } + if frontendDevServerURL != "" { if devServer == "" { return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address") @@ -107,7 +116,7 @@ func CreateApp(appoptions *options.App) (*App, error) { } else { if assetdir == "" { // If no assetdir has been defined, let's try to infer it from the project root and the asset FS. - assetdir, err = tryInferAssetDirFromFS(appoptions.Assets) + assetdir, err = tryInferAssetDirFromFS(assetConfig.Assets) if err != nil { return nil, err } @@ -121,12 +130,17 @@ func CreateApp(appoptions *options.App) (*App, error) { } myLogger.Info("Serving assets from disk: %s", absdir) - appoptions.Assets = os.DirFS(absdir) + assetConfig.Assets = os.DirFS(absdir) ctx = context.WithValue(ctx, "assetdir", assetdir) } } + // Migrate deprecated options to the new AssetServer option + appoptions.Assets = nil + appoptions.AssetsHandler = nil + appoptions.AssetServer = &assetConfig + if devServer != "" { ctx = context.WithValue(ctx, "devserver", devServer) } diff --git a/v2/internal/frontend/assetserver/assethandler.go b/v2/internal/frontend/assetserver/assethandler.go index ca73be4d2..09353aa3f 100644 --- a/v2/internal/frontend/assetserver/assethandler.go +++ b/v2/internal/frontend/assetserver/assethandler.go @@ -14,6 +14,7 @@ import ( "github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed defaultindex.html @@ -32,7 +33,13 @@ type assetHandler struct { retryMissingFiles bool } -func NewAssetHandler(ctx context.Context, vfs iofs.FS, assetsHandler http.Handler) (http.Handler, error) { +func NewAssetHandler(ctx context.Context, options assetserver.Options) (http.Handler, error) { + var log *logger.Logger + if _logger := ctx.Value("logger"); _logger != nil { + log = _logger.(*logger.Logger) + } + + vfs := options.Assets if vfs != nil { if _, err := vfs.Open("."); err != nil { return nil, err @@ -49,13 +56,14 @@ func NewAssetHandler(ctx context.Context, vfs iofs.FS, assetsHandler http.Handle } } - result := &assetHandler{ + var result http.Handler = &assetHandler{ fs: vfs, - handler: assetsHandler, + handler: options.Handler, + logger: log, } - if _logger := ctx.Value("logger"); _logger != nil { - result.logger = _logger.(*logger.Logger) + if middleware := options.Middleware; middleware != nil { + result = middleware(result) } return result, nil diff --git a/v2/internal/frontend/assetserver/assetserver.go b/v2/internal/frontend/assetserver/assetserver.go index 5fff282f2..fd66d46bd 100644 --- a/v2/internal/frontend/assetserver/assetserver.go +++ b/v2/internal/frontend/assetserver/assetserver.go @@ -4,15 +4,16 @@ import ( "bytes" "context" "fmt" - iofs "io/fs" "net/http" "net/http/httptest" "strconv" + "golang.org/x/net/html" + "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" - - "golang.org/x/net/html" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) const ( @@ -22,6 +23,7 @@ const ( type AssetServer struct { handler http.Handler + wsHandler http.Handler runtimeJS []byte ipcJS func(*http.Request) []byte @@ -31,8 +33,12 @@ type AssetServer struct { appendSpinnerToBody bool } -func NewAssetServer(ctx context.Context, vfs iofs.FS, assetsHandler http.Handler, bindingsJSON string) (*AssetServer, error) { - handler, err := NewAssetHandler(ctx, vfs, assetsHandler) +func NewAssetServerMainPage(ctx context.Context, bindingsJSON string, options *options.App) (*AssetServer, error) { + return NewAssetServer(ctx, bindingsJSON, BuildAssetServerConfig(options)) +} + +func NewAssetServer(ctx context.Context, bindingsJSON string, options assetserver.Options) (*AssetServer, error) { + handler, err := NewAssetHandler(ctx, options) if err != nil { return nil, err } @@ -66,17 +72,21 @@ func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindin } func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if isWebSocket(req) { + // Forward WebSockets to the distinct websocket handler if it exists + if wsHandler := d.wsHandler; wsHandler != nil { + wsHandler.ServeHTTP(rw, req) + } else { + rw.WriteHeader(http.StatusNotImplemented) + } + return + } + header := rw.Header() if d.servingFromDisk { header.Add(HeaderCacheControl, "no-cache") } - if isWebSocket(req) { - // WebSockets can always directly be forwarded to the handler - d.handler.ServeHTTP(rw, req) - return - } - path := req.URL.Path switch path { case "", "/", "/index.html": diff --git a/v2/internal/frontend/assetserver/assetserver_browser_dev.go b/v2/internal/frontend/assetserver/assetserver_dev.go similarity index 56% rename from v2/internal/frontend/assetserver/assetserver_browser_dev.go rename to v2/internal/frontend/assetserver/assetserver_dev.go index 6ced11787..5d83cf5fe 100644 --- a/v2/internal/frontend/assetserver/assetserver_browser_dev.go +++ b/v2/internal/frontend/assetserver/assetserver_dev.go @@ -12,15 +12,17 @@ import ( ) /* -The assetserver for dev serves assets from disk. -It injects a websocket based IPC script into `index.html`. +The assetserver for the dev mode. +Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The +default desktop IPC is injected when the webview accesses the devserver. */ -func NewBrowserAssetServer(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) { +func NewDevAssetServer(ctx context.Context, handler http.Handler, wsHandler http.Handler, bindingsJSON string) (*AssetServer, error) { result, err := NewAssetServerWithHandler(ctx, handler, bindingsJSON) if err != nil { return nil, err } + result.wsHandler = wsHandler result.appendSpinnerToBody = true result.ipcJS = func(req *http.Request) []byte { if strings.Contains(req.UserAgent(), WailsUserAgentValue) { diff --git a/v2/internal/frontend/assetserver/common.go b/v2/internal/frontend/assetserver/common.go index b787cc6bb..ffe80577a 100644 --- a/v2/internal/frontend/assetserver/common.go +++ b/v2/internal/frontend/assetserver/common.go @@ -8,9 +8,26 @@ import ( "net/http" "strings" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" "golang.org/x/net/html" ) +func BuildAssetServerConfig(options *options.App) assetserver.Options { + if opts := options.AssetServer; opts != nil { + if options.Assets != nil || options.AssetsHandler != nil { + panic("It's not possible to use the deprecated Assets and AssetsHandler options and the new AssetServer option at the same time. Please migrate all your Assets options to the AssetServer option.") + } + + return *opts + } + + return assetserver.Options{ + Assets: options.Assets, + Handler: options.AssetsHandler, + } +} + const ( HeaderHost = "Host" HeaderContentType = "Content-Type" diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go index ca9975749..a1eff8e8d 100644 --- a/v2/internal/frontend/desktop/darwin/frontend.go +++ b/v2/internal/frontend/desktop/darwin/frontend.go @@ -90,7 +90,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } else { appBindings.DB().UpdateObfuscatedCallMap() } - assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings) + assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions) if err != nil { log.Fatal(err) } diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 6457bc984..bb934d343 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -94,7 +94,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } else { appBindings.DB().UpdateObfuscatedCallMap() } - assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings) + assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions) if err != nil { log.Fatal(err) } diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index 7135f5d03..f5eb3b92d 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -102,7 +102,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. appBindings.DB().UpdateObfuscatedCallMap() } - assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings) + assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions) if err != nil { log.Fatal(err) } @@ -435,9 +435,8 @@ func (f *Frontend) setupChromium() { log.Fatal(err) } - - if opts := f.frontendOptions.Windows; opts != nil { - if opts.ZoomFactor > 0.0 { + if opts := f.frontendOptions.Windows; opts != nil { + if opts.ZoomFactor > 0.0 { chromium.PutZoomFactor(opts.ZoomFactor) } err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled) diff --git a/v2/internal/frontend/devserver/devserver.go b/v2/internal/frontend/devserver/devserver.go index 3cfebd587..6dc18b8e0 100644 --- a/v2/internal/frontend/devserver/devserver.go +++ b/v2/internal/frontend/devserver/devserver.go @@ -12,6 +12,7 @@ import ( "log" "net" "net/http" + "net/http/httputil" "net/url" "strings" "sync" @@ -53,7 +54,10 @@ func (d *DevWebServer) Run(ctx context.Context) error { d.server.GET("/wails/reload", d.handleReload) d.server.GET("/wails/ipc", d.handleIPCWebSocket) + assetServerConfig := assetserver.BuildAssetServerConfig(d.appoptions) + var assetHandler http.Handler + var wsHandler http.Handler _fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string) if _fronendDevServerURL == "" { assetdir, _ := ctx.Value("assetdir").(string) @@ -62,7 +66,7 @@ func (d *DevWebServer) Run(ctx context.Context) error { }) var err error - assetHandler, err = assetserver.NewAssetHandler(ctx, d.appoptions.Assets, d.appoptions.AssetsHandler) + assetHandler, err = assetserver.NewAssetHandler(ctx, assetServerConfig) if err != nil { log.Fatal(err) } @@ -81,7 +85,11 @@ func (d *DevWebServer) Run(ctx context.Context) error { d.logger.Error("Timeout waiting for frontend DevServer") } - assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, d.appoptions.AssetsHandler) + assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, assetServerConfig) + // WebSockets aren't currently supported in prod mode, so a WebSocket connection is the result of the + // FrontendDevServer e.g. Vite to support auto reloads. + // Therefore we direct WebSockets directly to the FrontendDevServer instead of returning a NotImplementedStatus. + wsHandler = httputil.NewSingleHostReverseProxy(externalURL) } // Setup internal dev server @@ -90,7 +98,7 @@ func (d *DevWebServer) Run(ctx context.Context) error { log.Fatal(err) } - assetServer, err := assetserver.NewBrowserAssetServer(ctx, assetHandler, bindingsJSON) + assetServer, err := assetserver.NewDevAssetServer(ctx, assetHandler, wsHandler, bindingsJSON) if err != nil { log.Fatal(err) } diff --git a/v2/internal/frontend/devserver/external.go b/v2/internal/frontend/devserver/external.go index a07b1c0d6..fd717e723 100644 --- a/v2/internal/frontend/devserver/external.go +++ b/v2/internal/frontend/devserver/external.go @@ -10,11 +10,21 @@ import ( "net/http/httputil" "net/url" - "github.com/labstack/echo/v4" "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) -func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler { +func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler { + handler := newExternalAssetsHandler(logger, url, options.Handler) + + if middleware := options.Middleware; middleware != nil { + handler = middleware(handler) + } + + return handler +} + +func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler { errSkipProxy := fmt.Errorf("skip proxying") proxy := httputil.NewSingleHostReverseProxy(url) @@ -56,23 +66,18 @@ func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, handl } } - e := echo.New() - e.Any("/*", - func(c echo.Context) error { - req := c.Request() - rw := c.Response() - if c.IsWebSocket() || req.Method == http.MethodGet { + return http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodGet { proxy.ServeHTTP(rw, req) - return nil + return } if handler != nil { handler.ServeHTTP(rw, req) - return nil + return } - return c.NoContent(http.StatusMethodNotAllowed) + rw.WriteHeader(http.StatusMethodNotAllowed) }) - - return e } diff --git a/v2/internal/staticanalysis/test/standard/main.go b/v2/internal/staticanalysis/test/standard/main.go index 93cbc7ea3..3f735d640 100644 --- a/v2/internal/staticanalysis/test/standard/main.go +++ b/v2/internal/staticanalysis/test/standard/main.go @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "staticanalysis", - Width: 1024, - Height: 768, - Assets: assets, + Title: "staticanalysis", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/options/assetserver/middleware.go b/v2/pkg/options/assetserver/middleware.go new file mode 100644 index 000000000..b3826ab7d --- /dev/null +++ b/v2/pkg/options/assetserver/middleware.go @@ -0,0 +1,20 @@ +package assetserver + +import ( + "net/http" +) + +// Middleware defines a HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v2/pkg/options/assetserver/options.go b/v2/pkg/options/assetserver/options.go new file mode 100644 index 000000000..0be9a1d3e --- /dev/null +++ b/v2/pkg/options/assetserver/options.go @@ -0,0 +1,35 @@ +package assetserver + +import ( + "io/fs" + "net/http" +) + +// Options defines the configuration of the AssetServer. +type Options struct { + // Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + Assets fs.FS + + // Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware +} diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index 25f619b6b..e6ded464a 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -8,6 +8,7 @@ import ( "net/http" "runtime" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" "github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/windows" @@ -47,9 +48,13 @@ type App struct { AlwaysOnTop bool // BackgroundColour is the background colour of the window // You can use the options.NewRGB and options.NewRGBA functions to create a new colour - BackgroundColour *RGBA - Assets fs.FS - AssetsHandler http.Handler + BackgroundColour *RGBA + // Deprecated: Use AssetServer.Assets instead. + Assets fs.FS + // Deprecated: Use AssetServer.Handler instead. + AssetsHandler http.Handler + // AssetServer configures the Assets for the application + AssetServer *assetserver.Options Menu *menu.Menu Logger logger.Logger `json:"-"` LogLevel logger.LogLevel diff --git a/v2/pkg/templates/base/main.go.tmpl b/v2/pkg/templates/base/main.go.tmpl index a8324d1b8..571cf6b10 100644 --- a/v2/pkg/templates/base/main.go.tmpl +++ b/v2/pkg/templates/base/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" +"github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed frontend/dist @@ -16,10 +17,12 @@ app := NewApp() // Create application with options err := wails.Run(&options.App{ -Title: "{{.ProjectName}}", -Width: 1024, -Height: 768, -Assets: assets, +Title: "{{.ProjectName}}", +Width: 1024, +Height: 768, +AssetServer: &assetserver.Options{ + Assets: assets, +}, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/generate/plain/main.go.tmpl b/v2/pkg/templates/generate/plain/main.go.tmpl index d13de5a38..9f3e2fffe 100644 --- a/v2/pkg/templates/generate/plain/main.go.tmpl +++ b/v2/pkg/templates/generate/plain/main.go.tmpl @@ -9,6 +9,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/options" +"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/windows" ) @@ -37,7 +38,9 @@ Frameless: false, StartHidden: false, HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, -Assets: assets, +AssetServer: &assetserver.Options{ + Assets: assets, +}, Menu: nil, Logger: nil, LogLevel: logger.DEBUG, diff --git a/v2/pkg/templates/templates/lit-ts/main.go.tmpl b/v2/pkg/templates/templates/lit-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/lit-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/lit-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/lit/main.go.tmpl b/v2/pkg/templates/templates/lit/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/lit/main.go.tmpl +++ b/v2/pkg/templates/templates/lit/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/plain/main.go.tmpl b/v2/pkg/templates/templates/plain/main.go.tmpl index 09d0b8ea3..847803db6 100644 --- a/v2/pkg/templates/templates/plain/main.go.tmpl +++ b/v2/pkg/templates/templates/plain/main.go.tmpl @@ -5,6 +5,7 @@ import ( "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 @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/preact-ts/main.go.tmpl b/v2/pkg/templates/templates/preact-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/preact-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/preact-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/preact/main.go.tmpl b/v2/pkg/templates/templates/preact/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/preact/main.go.tmpl +++ b/v2/pkg/templates/templates/preact/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/react-ts/main.go.tmpl b/v2/pkg/templates/templates/react-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/react-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/react-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/react/main.go.tmpl b/v2/pkg/templates/templates/react/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/react/main.go.tmpl +++ b/v2/pkg/templates/templates/react/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/svelte-ts/main.go.tmpl b/v2/pkg/templates/templates/svelte-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/svelte-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/svelte-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/svelte/main.go.tmpl b/v2/pkg/templates/templates/svelte/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/svelte/main.go.tmpl +++ b/v2/pkg/templates/templates/svelte/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/vanilla-ts/main.go.tmpl b/v2/pkg/templates/templates/vanilla-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/vanilla-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/vanilla-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/vanilla/main.go.tmpl b/v2/pkg/templates/templates/vanilla/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/vanilla/main.go.tmpl +++ b/v2/pkg/templates/templates/vanilla/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/vue-ts/main.go.tmpl b/v2/pkg/templates/templates/vue-ts/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/vue-ts/main.go.tmpl +++ b/v2/pkg/templates/templates/vue-ts/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/v2/pkg/templates/templates/vue/main.go.tmpl b/v2/pkg/templates/templates/vue/main.go.tmpl index e0f197dee..e24782be3 100644 --- a/v2/pkg/templates/templates/vue/main.go.tmpl +++ b/v2/pkg/templates/templates/vue/main.go.tmpl @@ -5,6 +5,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -16,10 +17,12 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "{{.ProjectName}}", - Width: 1024, - Height: 768, - Assets: assets, + Title: "{{.ProjectName}}", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ diff --git a/website/docs/guides/dynamic-assets.mdx b/website/docs/guides/dynamic-assets.mdx index 07202e0a4..806accf18 100644 --- a/website/docs/guides/dynamic-assets.mdx +++ b/website/docs/guides/dynamic-assets.mdx @@ -19,6 +19,7 @@ import ( "fmt" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" "net/http" "os" "strings" @@ -54,13 +55,15 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "helloworld", - Width: 1024, - Height: 768, - Assets: assets, + Title: "helloworld", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + Handler: NewFileLoader(), + }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 255}, OnStartup: app.startup, - AssetsHandler: NewFileLoader(), Bind: []interface{}{ app, }, diff --git a/website/docs/guides/frameless.mdx b/website/docs/guides/frameless.mdx index 4d5798474..07d8d2d25 100644 --- a/website/docs/guides/frameless.mdx +++ b/website/docs/guides/frameless.mdx @@ -38,6 +38,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -49,15 +50,17 @@ func main() { // Create application with options err := wails.Run(&options.App{ - Title: "alwaysontop", - Width: 1024, - Height: 768, - Assets: assets, + Title: "alwaysontop", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Frameless: true, CSSDragProperty: "widows", CSSDragValue: "1", Bind: []interface{}{ - app, + app, }, }) diff --git a/website/docs/guides/migrating.mdx b/website/docs/guides/migrating.mdx index 1fcdcc958..86549707d 100644 --- a/website/docs/guides/migrating.mdx +++ b/website/docs/guides/migrating.mdx @@ -31,7 +31,9 @@ In v2, there is just a single method, `wails.Run()`, that accepts [application o Title: "MyApp", Width: 800, Height: 600, - Assets: assets, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Bind: []interface{}{ basic, }, @@ -165,7 +167,9 @@ var assets embed.FS func main() { err := wails.Run(&options.App{ /* Other Options */ - Assets: assets, + AssetServer: &assetserver.Options{ + Assets: assets, + }, }) } ``` diff --git a/website/docs/howdoesitwork.mdx b/website/docs/howdoesitwork.mdx index 872e847f3..fb23b3dfc 100644 --- a/website/docs/howdoesitwork.mdx +++ b/website/docs/howdoesitwork.mdx @@ -33,6 +33,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -43,12 +44,14 @@ func main() { app := &App{} err := wails.Run(&options.App{ - Title: "Basic Demo", - Width: 1024, - Height: 768, - Assets: &assets, - OnStartup: app.startup, - OnShutdown: app.shutdown, + Title: "Basic Demo", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + OnStartup: app.startup, + OnShutdown: app.shutdown, Bind: []interface{}{ app, }, @@ -147,6 +150,7 @@ import ( "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) //go:embed all:frontend/dist @@ -157,10 +161,12 @@ func main() { app := &App{} err := wails.Run(&options.App{ - Title: "Basic Demo", - Width: 1024, - Height: 768, - Assets: &assets, + Title: "Basic Demo", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Bind: []interface{}{ app, }, @@ -185,10 +191,12 @@ You may bind as many structs as you like. Just make sure you create an instance ```go {8-10} //... err := wails.Run(&options.App{ - Title: "Basic Demo", - Width: 1024, - Height: 768, - Assets: &assets, + Title: "Basic Demo", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, Bind: []interface{}{ app, &mystruct1{}, diff --git a/website/docs/reference/options.mdx b/website/docs/reference/options.mdx index 4f2b1227a..baeb92876 100644 --- a/website/docs/reference/options.mdx +++ b/website/docs/reference/options.mdx @@ -10,7 +10,13 @@ The `Options.App` struct contains the application configuration. It is passed to the `wails.Run()` method: ```go title="Example" -import "github.com/wailsapp/wails/v2/pkg/options" +import ( + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v2/pkg/options/linux" + "github.com/wailsapp/wails/v2/pkg/options/mac" + "github.com/wailsapp/wails/v2/pkg/options/windows" +) func main() { @@ -29,8 +35,11 @@ func main() { HideWindowOnClose: false, BackgroundColour: &options.RGBA{R: 0, G: 0, B: 0, A: 255}, AlwaysOnTop: false, - Assets: assets, - AssetsHandler: assetsHandler, + AssetServer: &assetserver.Options{ + Assets: assets, + Handler: assetsHandler, + Middleware: assetsMidldeware, + }, Menu: app.applicationMenu(), Logger: nil, LogLevel: logger.DEBUG, @@ -213,19 +222,20 @@ Type: `bool` ### Assets -The frontend assets to be used by the application. Requires an `index.html` file. - -Name: Assets
-Type: `embed.FS` +Deprecated: Please use Assets on [AssetServer specific options](#assetserver). ### AssetsHandler - +Deprecated: Please use AssetsHandler on [AssetServer specific options](#assetserver). -The assets handler is a generic `http.Handler` which will be called for any non GET request on the assets server -and for GET requests which can not be served from the `assets` because the file is not found. +### AssetServer -| Value | Win | Mac | Lin | +This defines AssetServer specific options. It allows to customize the AssetServer with static assets, serving assets +dynamically with an `http.Handler` or hook into the request chain with an `assetserver.Middleware`. + +Not all features of an `http.Request` are currently supported, please see the following feature matrix: + +| Feature | Win | Mac | Lin | | ----------------------- | --- | --- | --- | | GET | ✅ | ✅ | ✅ | | POST | ✅ | ✅ | ❌ | @@ -239,16 +249,55 @@ and for GET requests which can not be served from the `assets` because the file | Response Headers | ✅ | ✅ | ❌ | | Response Body | ✅ | ✅ | ✅ | | Response Body Streaming | ❌ | ❌ | ✅ | +| WebSockets | ❌ | ❌ | ❌ | NOTE: Linux is currently very limited due to targeting a WebKit2GTK Version < 2.36.0. In the future some features will be supported by the introduction of WebKit2GTK 2.36.0+ support. +Name: AssetServer
+Type: `*assetserver.Options` + +#### Assets + +The static frontend assets to be used by the application. + +A GET request is first tried to be served from this `fs.FS`. If the `fs.FS` returns `os.ErrNotExist` for that file, +the request handling will fallback to the [Handler](#handler) and tries to serve the GET request from it. + +If set to nil, all GET requests will be forwarded to [Handler](#handler). + +Name: Assets
+Type: `fs.FS` + +#### Handler + +The assets handler is a generic `http.Handler` for fallback handling of assets that can't be found. + +The handler will be called for every GET request that can't be served from [Assets](#assets), due to `os.ErrNotExist`. +Furthermore all non GET requests will always be served from this Handler. + +If not defined, the result is the following in cases where the Handler would have been called: +- GET request: `http.StatusNotFound` +- Other request: `http.StatusMethodNotAllowed` + NOTE: When used in combination with a Frontend DevServer there might be limitations, eg. Vite serves the index.html on every path, that does not contain a file extension. Name: AssetsHandler
Type: `http.Handler` +#### Middleware + +Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default +request handler dynamically, e.g. implement specialized Routing etc. +The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default +handler used by the AssetServer as an argument. + +If not defined, the default AssetServer request chain is executed. + +Name: Middleware
+Type: `assetserver.Middleware` + ### Menu The menu to be used by the application. More details about Menus in the [Menu Reference](../reference/runtime/menu.mdx).