From 6fcd4b7bd4ef6b9ccaf1b5de4c464673425c7af5 Mon Sep 17 00:00:00 2001 From: stffabi Date: Fri, 3 Dec 2021 10:25:38 +0100 Subject: [PATCH] [v2] Support fs.FS for assets Reloading changed asset files in dev mode will only work if an embed.FS has been provided for the assets. --- .../assetserver/assetserver_browser_dev.go | 49 +++++++--- .../assetserver/assetserver_common.go | 64 ++++++++++++ .../assetserver/assetserver_common_dev.go | 57 +++++++++++ .../assetserver/assetserver_common_nodev.go | 12 +++ .../assetserver/assetserver_desktop.go | 97 ++++--------------- .../assetserver/assetserver_desktop_dev.go | 13 --- .../assetserver_desktop_production.go | 7 -- v2/internal/frontend/devserver/devserver.go | 26 ++--- v2/pkg/options/options.go | 6 +- 9 files changed, 196 insertions(+), 135 deletions(-) create mode 100644 v2/internal/frontend/assetserver/assetserver_common.go create mode 100644 v2/internal/frontend/assetserver/assetserver_common_dev.go create mode 100644 v2/internal/frontend/assetserver/assetserver_common_nodev.go delete mode 100644 v2/internal/frontend/assetserver/assetserver_desktop_dev.go delete mode 100644 v2/internal/frontend/assetserver/assetserver_desktop_production.go diff --git a/v2/internal/frontend/assetserver/assetserver_browser_dev.go b/v2/internal/frontend/assetserver/assetserver_browser_dev.go index 296f98a71..0363bcd05 100644 --- a/v2/internal/frontend/assetserver/assetserver_browser_dev.go +++ b/v2/internal/frontend/assetserver/assetserver_browser_dev.go @@ -5,8 +5,12 @@ package assetserver import ( "bytes" + "context" + "io/fs" + "strings" + "github.com/wailsapp/wails/v2/internal/frontend/runtime" - "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/internal/logger" "golang.org/x/net/html" "path/filepath" ) @@ -18,35 +22,42 @@ It injects a websocket based IPC script into `index.html`. */ -import ( - "os" -) - type BrowserAssetServer struct { - runtimeJS []byte - assetdir string - appOptions *options.App + assets fs.FS + runtimeJS []byte + logger *logger.Logger + fromDisk bool } -func NewBrowserAssetServer(assetdir string, bindingsJSON string, appOptions *options.App) (*BrowserAssetServer, error) { - result := &BrowserAssetServer{ - assetdir: assetdir, - appOptions: appOptions, +func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*BrowserAssetServer, error) { + result := &BrowserAssetServer{} + _logger := ctx.Value("logger") + if _logger != nil { + result.logger = _logger.(*logger.Logger) + } + + var err error + result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "BrowserAssetServer", assets) + if err != nil { + return nil, err } var buffer bytes.Buffer buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") buffer.Write(runtime.RuntimeDesktopJS) result.runtimeJS = buffer.Bytes() + return result, nil } -func (a *BrowserAssetServer) loadFileFromDisk(filename string) ([]byte, error) { - return os.ReadFile(filepath.Join(a.assetdir, filename)) +func (d *BrowserAssetServer) LogDebug(message string, args ...interface{}) { + if d.logger != nil { + d.logger.Debug("[BrowserAssetServer] "+message, args...) + } } func (a *BrowserAssetServer) processIndexHTML() ([]byte, error) { - indexHTML, err := a.loadFileFromDisk("index.html") + indexHTML, err := fs.ReadFile(a.assets, "index.html") if err != nil { return nil, err } @@ -96,7 +107,13 @@ func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) { case "/wails/ipc.js": content = runtime.WebsocketIPC default: - content, err = a.loadFileFromDisk(filename) + filename = strings.TrimPrefix(filename, "/") + fromDisk := "" + if a.fromDisk { + fromDisk = " (disk)" + } + a.LogDebug("Loading file: %s%s", filename, fromDisk) + content, err = fs.ReadFile(a.assets, filename) } if err != nil { return nil, "", err diff --git a/v2/internal/frontend/assetserver/assetserver_common.go b/v2/internal/frontend/assetserver/assetserver_common.go new file mode 100644 index 000000000..09e0c3c06 --- /dev/null +++ b/v2/internal/frontend/assetserver/assetserver_common.go @@ -0,0 +1,64 @@ +package assetserver + +import ( + "context" + "fmt" + "io/fs" + "path" + "path/filepath" + "strings" + + "github.com/leaanthony/slicer" +) + +func prepareAssetsForServing(ctx context.Context, serverType string, assets fs.FS) (fs.FS, bool, error) { + // Let's check if we need an assetdir override + if assetdir, err := isAssetDirOverride(ctx, serverType, assets); err != nil { + return nil, false, err + } else if assetdir != nil { + return assetdir, true, nil + } + + // Otherwise let's search for the index.html + if _, err := assets.Open("."); err != nil { + return nil, false, err + } + + subDir, err := pathToIndexHTML(assets) + if err != nil { + return nil, false, err + } + + assets, err = fs.Sub(assets, path.Clean(subDir)) + if err != nil { + return nil, false, err + } + return assets, false, nil +} + +func pathToIndexHTML(assets fs.FS) (string, error) { + stat, _ := fs.Stat(assets, "index.html") + if stat != nil { + return ".", nil + } + var indexFiles slicer.StringSlicer + err := fs.WalkDir(assets, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, "index.html") { + indexFiles.Add(path) + } + return nil + }) + if err != nil { + return "", err + } + + if indexFiles.Length() > 1 { + return "", fmt.Errorf("multiple 'index.html' files found in assets") + } + + path, _ := filepath.Split(indexFiles.AsSlice()[0]) + return path, nil +} diff --git a/v2/internal/frontend/assetserver/assetserver_common_dev.go b/v2/internal/frontend/assetserver/assetserver_common_dev.go new file mode 100644 index 000000000..51cbc6ba2 --- /dev/null +++ b/v2/internal/frontend/assetserver/assetserver_common_dev.go @@ -0,0 +1,57 @@ +//go:build dev +// +build dev + +package assetserver + +import ( + "context" + "embed" + "io/fs" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v2/internal/logger" +) + +func isAssetDirOverride(ctx context.Context, serverType string, assets fs.FS) (fs.FS, error) { + _logger, _ := ctx.Value("logger").(*logger.Logger) + logWarning := func(msg string) { + if _logger == nil { + return + } + + _logger.Warning("[%s] Reloading changed asset files won't work: %s", serverType, msg) + } + + _, isEmbedFs := assets.(embed.FS) + if assetdir := ctx.Value("assetdir"); assetdir == nil { + // We are in dev mode, but have no assetdir, let's check the type of the assets FS. + if isEmbedFs { + logWarning("A 'embed.FS' has been provided, but no assetdir has been defined") + } else { + // That's fine, the user is using another fs.FS and also has not set an assetdir + } + } else { + // We are in dev mode and an assetdir has been defined + if isEmbedFs { + // If the fs.FS is an embed.FS we assume it's serving the files from this directory, therefore replace the assets + // fs.FS with this directory. + // Are there any better ways to detect this? If we could somehow determine the compile time source path for the + // embed.FS this would be great. Currently there seems no way to get to this information. + absdir, err := filepath.Abs(assetdir.(string)) + if err != nil { + return nil, err + } + + if _logger != nil { + _logger.Debug("[%s] Serving assets from disk: %s", serverType, absdir) + } + + return os.DirFS(absdir), nil + } else { + logWarning("An assetdir has been defined, but no 'embed.FS' has been provided") + } + } + + return nil, nil +} diff --git a/v2/internal/frontend/assetserver/assetserver_common_nodev.go b/v2/internal/frontend/assetserver/assetserver_common_nodev.go new file mode 100644 index 000000000..2d9fd7187 --- /dev/null +++ b/v2/internal/frontend/assetserver/assetserver_common_nodev.go @@ -0,0 +1,12 @@ +//go:build !dev + +package assetserver + +import ( + "context" + "io/fs" +) + +func isAssetDirOverride(ctx context.Context, serverType string, assets fs.FS) (fs.FS, error) { + return nil, nil +} diff --git a/v2/internal/frontend/assetserver/assetserver_desktop.go b/v2/internal/frontend/assetserver/assetserver_desktop.go index 1ec4a7532..d48e80973 100644 --- a/v2/internal/frontend/assetserver/assetserver_desktop.go +++ b/v2/internal/frontend/assetserver/assetserver_desktop.go @@ -3,26 +3,22 @@ package assetserver import ( "bytes" "context" - "embed" - "fmt" - "github.com/leaanthony/debme" - "github.com/leaanthony/slicer" - "github.com/wailsapp/wails/v2/internal/frontend/runtime" - "github.com/wailsapp/wails/v2/internal/logger" "io/fs" "log" - "path/filepath" "strings" + + "github.com/wailsapp/wails/v2/internal/frontend/runtime" + "github.com/wailsapp/wails/v2/internal/logger" ) type DesktopAssetServer struct { - assets debme.Debme + assets fs.FS runtimeJS []byte - assetdir string logger *logger.Logger + fromDisk bool } -func NewDesktopAssetServer(ctx context.Context, assets embed.FS, bindingsJSON string) (*DesktopAssetServer, error) { +func NewDesktopAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*DesktopAssetServer, error) { result := &DesktopAssetServer{} _logger := ctx.Value("logger") @@ -30,22 +26,18 @@ func NewDesktopAssetServer(ctx context.Context, assets embed.FS, bindingsJSON st result.logger = _logger.(*logger.Logger) } - _assetdir := ctx.Value("assetdir") - if _assetdir != nil { - result.assetdir = _assetdir.(string) - absdir, err := filepath.Abs(result.assetdir) - if err != nil { - return nil, err - } - result.LogDebug("Loading assets from: %s", absdir) + var err error + result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "DesktopAssetServer", assets) + if err != nil { + return nil, err } var buffer bytes.Buffer buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") buffer.Write(runtime.RuntimeDesktopJS) result.runtimeJS = buffer.Bytes() - err := result.init(assets) - return result, err + + return result, nil } func (d *DesktopAssetServer) LogDebug(message string, args ...interface{}) { @@ -54,63 +46,8 @@ func (d *DesktopAssetServer) LogDebug(message string, args ...interface{}) { } } -func (d *DesktopAssetServer) SetAssetDir(assetdir string) { - d.assetdir = assetdir -} - -func PathToIndexHTML(assets embed.FS) (string, error) { - stat, err := fs.Stat(assets, "index.html") - if stat != nil { - return ".", nil - } - var indexFiles slicer.StringSlicer - err = fs.WalkDir(assets, ".", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if strings.HasSuffix(path, "index.html") { - indexFiles.Add(path) - } - return nil - }) - if err != nil { - return "", err - } - - if indexFiles.Length() > 1 { - return "", fmt.Errorf("multiple 'index.html' files found in assets") - } - - path, _ := filepath.Split(indexFiles.AsSlice()[0]) - return path, nil -} - -func processAssets(assets embed.FS) (debme.Debme, error) { - - result, err := debme.FS(assets, ".") - if err != nil { - return result, err - } - // Find index.html - path, err := PathToIndexHTML(assets) - if err != nil { - return debme.Debme{}, err - } - return debme.FS(assets, path) -} - -func (a *DesktopAssetServer) init(assets embed.FS) error { - - var err error - a.assets, err = processAssets(assets) - if err != nil { - return err - } - return nil -} - func (a *DesktopAssetServer) processIndexHTML() ([]byte, error) { - indexHTML, err := a.ReadFile("index.html") + indexHTML, err := fs.ReadFile(a.assets, "index.html") if err != nil { return nil, err } @@ -146,7 +83,13 @@ func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) { case "/wails/ipc.js": content = runtime.DesktopIPC default: - content, err = a.ReadFile(filename) + filename = strings.TrimPrefix(filename, "/") + fromDisk := "" + if a.fromDisk { + fromDisk = " (disk)" + } + a.LogDebug("Loading file: %s%s", filename, fromDisk) + content, err = fs.ReadFile(a.assets, filename) } if err != nil { return nil, "", err diff --git a/v2/internal/frontend/assetserver/assetserver_desktop_dev.go b/v2/internal/frontend/assetserver/assetserver_desktop_dev.go deleted file mode 100644 index 42726ae3a..000000000 --- a/v2/internal/frontend/assetserver/assetserver_desktop_dev.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build dev - -package assetserver - -import ( - "os" - "path/filepath" -) - -func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) { - a.LogDebug("Loading file from disk: %s", filename) - return os.ReadFile(filepath.Join(a.assetdir, filename)) -} diff --git a/v2/internal/frontend/assetserver/assetserver_desktop_production.go b/v2/internal/frontend/assetserver/assetserver_desktop_production.go deleted file mode 100644 index fb09ee1a3..000000000 --- a/v2/internal/frontend/assetserver/assetserver_desktop_production.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build production - -package assetserver - -func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) { - return a.assets.ReadFile(filename) -} diff --git a/v2/internal/frontend/devserver/devserver.go b/v2/internal/frontend/devserver/devserver.go index e73828c5d..4b9885fe3 100644 --- a/v2/internal/frontend/devserver/devserver.go +++ b/v2/internal/frontend/devserver/devserver.go @@ -10,7 +10,6 @@ import ( "fmt" "io/fs" "log" - "path/filepath" "strings" "sync" "time" @@ -100,25 +99,14 @@ func (d *DevWebServer) Run(ctx context.Context) error { _devServerURL := ctx.Value("devserverurl") if _devServerURL == "http://localhost:34115" { // Setup internal dev server - _assetdir := ctx.Value("assetdir") - if _assetdir == nil { - return fmt.Errorf("no assetdir provided") + bindingsJSON, err := d.appBindings.ToJSON() + if err != nil { + log.Fatal(err) } - if _assetdir != nil { - assetdir := _assetdir.(string) - bindingsJSON, err := d.appBindings.ToJSON() - if err != nil { - log.Fatal(err) - } - d.assetServer, err = assetserver.NewBrowserAssetServer(assetdir, bindingsJSON, d.appoptions) - if err != nil { - log.Fatal(err) - } - absdir, err := filepath.Abs(assetdir) - if err != nil { - return err - } - d.LogDebug("Serving assets from: %s", absdir) + + d.assetServer, err = assetserver.NewBrowserAssetServer(ctx, d.appoptions.Assets, bindingsJSON) + if err != nil { + log.Fatal(err) } d.server.Get("*", d.loadAsset) diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index 717fd93da..db224a5aa 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -2,11 +2,11 @@ package options import ( "context" - "embed" - "github.com/wailsapp/wails/v2/pkg/options/linux" + "io/fs" "log" "runtime" + "github.com/wailsapp/wails/v2/pkg/options/linux" "github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/windows" @@ -41,7 +41,7 @@ type App struct { HideWindowOnClose bool AlwaysOnTop bool RGBA *RGBA - Assets embed.FS + Assets fs.FS Menu *menu.Menu Logger logger.Logger `json:"-"` LogLevel logger.LogLevel