From 131a8f421dc2ed2db6a368cc0fe3a9db42961b25 Mon Sep 17 00:00:00 2001 From: stffabi Date: Mon, 6 Dec 2021 08:49:12 +0100 Subject: [PATCH] [v2] Infer assetDir from embed.FS AssetDir is now inferred from the assets, if the assets is an embed.FS, by taking the relativ path to the index.html joined with the project root. The assetDir flag still exists and can be used if the inferring doesn't work, because the provided embed.FS wasn't defined in the main package. --- v2/cmd/wails/internal/commands/dev/dev.go | 55 +++++++++++++----- .../template/base/NEXTSTEPS.md.template | 1 - .../generate/template/base/wails.tmpl.json | 1 - .../templates/ides/goland/workspace.tmpl.xml | 1 - .../templates/ides/vscode/launch.tmpl.json | 6 +- .../initialise/templates/templates.go | 19 ------- .../templates/svelte/wails.tmpl.json | 1 - .../templates/vanilla/wails.tmpl.json | 1 - v2/internal/appng/app_dev.go | 56 +++++++++++++++++- .../assetserver/assetserver_browser_dev.go | 10 +--- .../assetserver/assetserver_common.go | 57 +++---------------- .../assetserver/assetserver_common_dev.go | 57 ------------------- .../assetserver/assetserver_common_nodev.go | 12 ---- .../assetserver/assetserver_desktop.go | 9 +-- v2/internal/frontend/devserver/devserver.go | 5 ++ v2/internal/fs/fs.go | 29 ++++++++++ 16 files changed, 142 insertions(+), 178 deletions(-) delete mode 100644 v2/internal/frontend/assetserver/assetserver_common_dev.go delete mode 100644 v2/internal/frontend/assetserver/assetserver_common_nodev.go diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go index df452d1fe..349c21394 100644 --- a/v2/cmd/wails/internal/commands/dev/dev.go +++ b/v2/cmd/wails/internal/commands/dev/dev.go @@ -83,7 +83,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { flags := defaultDevFlags() command.StringFlag("ldflags", "optional ldflags", &flags.ldflags) command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &flags.compilerCommand) - command.StringFlag("assetdir", "Serve assets from the given directory", &flags.assetDir) + command.StringFlag("assetdir", "Serve assets from the given directory instead of using the provided asset FS", &flags.assetDir) command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go", &flags.extensions) command.BoolFlag("browser", "Open application in browser", &flags.openBrowser) command.BoolFlag("noreload", "Disable reload on asset change", &flags.noReload) @@ -301,10 +301,6 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e var shouldSaveConfig bool - if projectConfig.AssetDirectory == "" && flags.assetDir == "" { - return nil, fmt.Errorf("No asset directory provided. Please use -assetdir to indicate which directory contains your built assets.") - } - if flags.assetDir == "" && projectConfig.AssetDirectory != "" { flags.assetDir = projectConfig.AssetDirectory } @@ -313,9 +309,11 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e projectConfig.AssetDirectory = filepath.ToSlash(flags.assetDir) } - flags.assetDir, err = filepath.Abs(flags.assetDir) - if err != nil { - return nil, err + if flags.assetDir != "" { + flags.assetDir, err = filepath.Abs(flags.assetDir) + if err != nil { + return nil, err + } } if flags.devServerURL == defaultDevServerURL && projectConfig.DevServerURL != defaultDevServerURL && projectConfig.DevServerURL != "" { @@ -507,7 +505,8 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc interval := time.Duration(flags.debounceMS) * time.Millisecond timer := time.NewTimer(interval) rebuild := false - reload := false + assetDir := "" + changedPaths := map[string]struct{}{} for quit == false { //reload := false select { @@ -519,12 +518,13 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc // Check for file writes if item.Op&fsnotify.Write == fsnotify.Write { // Ignore directories - if fs.DirExists(item.Name) { + itemName := item.Name + if fs.DirExists(itemName) { continue } // Iterate all file patterns - ext := filepath.Ext(item.Name) + ext := filepath.Ext(itemName) if ext != "" { ext = ext[1:] if _, exists := extensionsThatTriggerARebuild[ext]; exists { @@ -534,9 +534,8 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc } } - if strings.HasPrefix(item.Name, flags.assetDir) { - reload = true - } + changedPaths[filepath.Dir(itemName)] = struct{}{} + timer.Reset(interval) } // Check for new directories @@ -568,6 +567,34 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc debugBinaryProcess = newBinaryProcess } } + reload := false + if len(changedPaths) != 0 { + if assetDir == "" { + resp, err := http.Get("http://localhost:34115/wails/assetdir") + if err != nil { + LogRed("Error during retrieving assetdir: %s", err.Error()) + } else { + content, err := io.ReadAll(resp.Body) + if err != nil { + LogRed("Error reading assetdir from devserver: %s", err.Error()) + } else { + assetDir = string(content) + } + resp.Body.Close() + } + } + + if assetDir != "" { + for path := range changedPaths { + if strings.HasPrefix(path, assetDir) { + reload = true + break + } + } + } + + changedPaths = map[string]struct{}{} + } if reload { reload = false _, err = http.Get("http://localhost:34115/wails/reload") diff --git a/v2/cmd/wails/internal/commands/generate/template/base/NEXTSTEPS.md.template b/v2/cmd/wails/internal/commands/generate/template/base/NEXTSTEPS.md.template index a357acd82..5363d10f2 100644 --- a/v2/cmd/wails/internal/commands/generate/template/base/NEXTSTEPS.md.template +++ b/v2/cmd/wails/internal/commands/generate/template/base/NEXTSTEPS.md.template @@ -10,7 +10,6 @@ The next steps to complete the template are: - It is really important to ensure `helpurl` is valid as this is where users of the template will be directed for help. 2. Update `README.md`. 3. Edit `wails.json` and ensure all fields are correct, especially: - - `assetdir` - path to your assets - `wailsjsdir` - path to generate wailsjs modules - `frontend:install` - The command to install your frontend dependencies - `frontend:build` - The command to build your frontend diff --git a/v2/cmd/wails/internal/commands/generate/template/base/wails.tmpl.json b/v2/cmd/wails/internal/commands/generate/template/base/wails.tmpl.json index b8d08108d..63c4e6fe7 100644 --- a/v2/cmd/wails/internal/commands/generate/template/base/wails.tmpl.json +++ b/v2/cmd/wails/internal/commands/generate/template/base/wails.tmpl.json @@ -1,7 +1,6 @@ { "name": "{{.ProjectName}}", "outputfilename": "{{.BinaryName}}", - "assetdir": "frontend/dist", "frontend:install": "npm install", "frontend:build": "npm run build", "author": { diff --git a/v2/cmd/wails/internal/commands/initialise/templates/ides/goland/workspace.tmpl.xml b/v2/cmd/wails/internal/commands/initialise/templates/ides/goland/workspace.tmpl.xml index 42f98045b..9727844b6 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/ides/goland/workspace.tmpl.xml +++ b/v2/cmd/wails/internal/commands/initialise/templates/ides/goland/workspace.tmpl.xml @@ -36,7 +36,6 @@ - diff --git a/v2/cmd/wails/internal/commands/initialise/templates/ides/vscode/launch.tmpl.json b/v2/cmd/wails/internal/commands/initialise/templates/ides/vscode/launch.tmpl.json index d03d5ccdb..005f9bc41 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/ides/vscode/launch.tmpl.json +++ b/v2/cmd/wails/internal/commands/initialise/templates/ides/vscode/launch.tmpl.json @@ -9,11 +9,7 @@ "program": "${workspaceFolder}/{{.PathToDesktopBinary}}", "preLaunchTask": "build", "cwd": "${workspaceFolder}", - "env": {}, - "args": [ - "-assetdir", - "{{.AssetDir}}" - ] + "env": {} } ] } \ No newline at end of file diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates.go b/v2/cmd/wails/internal/commands/initialise/templates/templates.go index 641916783..2c81e05be 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/templates.go +++ b/v2/cmd/wails/internal/commands/initialise/templates/templates.go @@ -42,7 +42,6 @@ type Data struct { AuthorNameAndEmail string WailsDirectory string GoSDKPath string - AssetDir string WindowsFlags string CGOEnabled string OutputFile string @@ -60,7 +59,6 @@ type Options struct { InitGit bool AuthorName string AuthorEmail string - AssetDir string IDE string ProjectNameFilename string // The project name but as a valid filename WailsVersion string @@ -261,7 +259,6 @@ func Install(options *Options) (bool, *Template, error) { AuthorName: options.AuthorName, WailsVersion: options.WailsVersion, GoSDKPath: options.GoSDKPath, - AssetDir: options.AssetDir, } // Create a formatted name and email combo. @@ -408,22 +405,6 @@ func installIDEFiles(o ideOptions) error { binaryName += ".exe" } - // Parse wails.json for assetdir - wailsJSONBytes, err := os.ReadFile(filepath.Join(o.options.TargetDir, "wails.json")) - if err != nil { - return err - } - var wailsJSON map[string]interface{} - err = json.Unmarshal(wailsJSONBytes, &wailsJSON) - if err != nil { - return err - } - assetDir := wailsJSON["assetdir"] - if assetDir == "" { - return fmt.Errorf("Unable to find 'assetdir' in 'wails.json' ") - } - - o.options.AssetDir = assetDir.(string) o.options.PathToDesktopBinary = filepath.ToSlash(filepath.Join("build", "bin", binaryName)) o.options.WindowsFlags = "" diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json index 5bf6b2a9b..eb4c38f71 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json +++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json @@ -1,7 +1,6 @@ { "name": "{{.ProjectName}}", "outputfilename": "{{.BinaryName}}", - "assetdir": "frontend/dist", "frontend:install": "npm install", "frontend:build": "npm run build", "wailsjsdir": "./frontend", diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/wails.tmpl.json b/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/wails.tmpl.json index a4f2bc2dd..b010f3203 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/wails.tmpl.json +++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/wails.tmpl.json @@ -1,7 +1,6 @@ { "name": "{{.ProjectName}}", "outputfilename": "{{.BinaryName}}", - "assetdir": "frontend/src", "wailsjsdir": "./frontend", "author": { "name": "{{.AuthorName}}", diff --git a/v2/internal/appng/app_dev.go b/v2/internal/appng/app_dev.go index b232e21dc..cf1bcdef7 100644 --- a/v2/internal/appng/app_dev.go +++ b/v2/internal/appng/app_dev.go @@ -5,7 +5,10 @@ package appng import ( "context" + "embed" "flag" + "fmt" + iofs "io/fs" "os" "path/filepath" @@ -91,12 +94,30 @@ func CreateApp(appoptions *options.App) (*App, error) { } } + 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) + if err != nil { + return nil, err + } + } + + if assetdir != "" { + // Let's override the assets to serve from on disk, if needed + absdir, err := filepath.Abs(assetdir) + if err != nil { + return nil, err + } + + myLogger.Info("Serving assets from disk: %s", absdir) + appoptions.Assets = os.DirFS(absdir) + + ctx = context.WithValue(ctx, "assetdir", assetdir) + } + if devServerURL != "" { ctx = context.WithValue(ctx, "devserverurl", devServerURL) } - if assetdir != "" { - ctx = context.WithValue(ctx, "assetdir", assetdir) - } if loglevel != "" { level, err := pkglogger.StringToLogLevel(loglevel) @@ -203,3 +224,32 @@ func generateBindings(bindings *binding.Bindings) error { return nil } + +func tryInferAssetDirFromFS(assets iofs.FS) (string, error) { + if _, isEmbedFs := assets.(embed.FS); !isEmbedFs { + // We only infer the assetdir for embed.FS assets + return "", nil + } + + path, err := fs.FindPathToFile(assets, "index.html") + if err != nil { + return "", err + } + + path, err = filepath.Abs(path) + if err != nil { + return "", err + } + + if _, err := os.Stat(filepath.Join(path, "index.html")); err != nil { + if os.IsNotExist(err) { + err = fmt.Errorf( + "inferred assetdir '%s' does not exist or does not contain an 'index.html' file, "+ + "please specify it with -assetdir or set it in wails.json", + path) + } + return "", err + } + + return path, nil +} diff --git a/v2/internal/frontend/assetserver/assetserver_browser_dev.go b/v2/internal/frontend/assetserver/assetserver_browser_dev.go index 0363bcd05..93de7607a 100644 --- a/v2/internal/frontend/assetserver/assetserver_browser_dev.go +++ b/v2/internal/frontend/assetserver/assetserver_browser_dev.go @@ -12,7 +12,6 @@ import ( "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" "golang.org/x/net/html" - "path/filepath" ) /* @@ -26,7 +25,6 @@ type BrowserAssetServer struct { assets fs.FS runtimeJS []byte logger *logger.Logger - fromDisk bool } func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*BrowserAssetServer, error) { @@ -37,7 +35,7 @@ func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON strin } var err error - result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "BrowserAssetServer", assets) + result.assets, err = prepareAssetsForServing(assets) if err != nil { return nil, err } @@ -108,11 +106,7 @@ func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) { content = runtime.WebsocketIPC default: filename = strings.TrimPrefix(filename, "/") - fromDisk := "" - if a.fromDisk { - fromDisk = " (disk)" - } - a.LogDebug("Loading file: %s%s", filename, fromDisk) + a.LogDebug("Loading file: %s", filename) content, err = fs.ReadFile(a.assets, filename) } if err != nil { diff --git a/v2/internal/frontend/assetserver/assetserver_common.go b/v2/internal/frontend/assetserver/assetserver_common.go index 09e0c3c06..d8ab4c9c6 100644 --- a/v2/internal/frontend/assetserver/assetserver_common.go +++ b/v2/internal/frontend/assetserver/assetserver_common.go @@ -1,64 +1,25 @@ package assetserver import ( - "context" - "fmt" - "io/fs" + iofs "io/fs" "path" - "path/filepath" - "strings" - "github.com/leaanthony/slicer" + "github.com/wailsapp/wails/v2/internal/fs" ) -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 +func prepareAssetsForServing(assets iofs.FS) (iofs.FS, error) { if _, err := assets.Open("."); err != nil { - return nil, false, err + return nil, err } - subDir, err := pathToIndexHTML(assets) + subDir, err := fs.FindPathToFile(assets, "index.html") if err != nil { - return nil, false, err + return nil, err } - assets, err = fs.Sub(assets, path.Clean(subDir)) + assets, err = iofs.Sub(assets, path.Clean(subDir)) if err != nil { - return nil, false, err + return nil, 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 + return assets, nil } diff --git a/v2/internal/frontend/assetserver/assetserver_common_dev.go b/v2/internal/frontend/assetserver/assetserver_common_dev.go deleted file mode 100644 index 51cbc6ba2..000000000 --- a/v2/internal/frontend/assetserver/assetserver_common_dev.go +++ /dev/null @@ -1,57 +0,0 @@ -//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 deleted file mode 100644 index 2d9fd7187..000000000 --- a/v2/internal/frontend/assetserver/assetserver_common_nodev.go +++ /dev/null @@ -1,12 +0,0 @@ -//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 d48e80973..6d4c15b22 100644 --- a/v2/internal/frontend/assetserver/assetserver_desktop.go +++ b/v2/internal/frontend/assetserver/assetserver_desktop.go @@ -15,7 +15,6 @@ type DesktopAssetServer struct { assets fs.FS runtimeJS []byte logger *logger.Logger - fromDisk bool } func NewDesktopAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*DesktopAssetServer, error) { @@ -27,7 +26,7 @@ func NewDesktopAssetServer(ctx context.Context, assets fs.FS, bindingsJSON strin } var err error - result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "DesktopAssetServer", assets) + result.assets, err = prepareAssetsForServing(assets) if err != nil { return nil, err } @@ -84,11 +83,7 @@ func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) { content = runtime.DesktopIPC default: filename = strings.TrimPrefix(filename, "/") - fromDisk := "" - if a.fromDisk { - fromDisk = " (disk)" - } - a.LogDebug("Loading file: %s%s", filename, fromDisk) + a.LogDebug("Loading file: %s", filename) content, err = fs.ReadFile(a.assets, filename) } if err != nil { diff --git a/v2/internal/frontend/devserver/devserver.go b/v2/internal/frontend/devserver/devserver.go index 4b9885fe3..3dcb84c56 100644 --- a/v2/internal/frontend/devserver/devserver.go +++ b/v2/internal/frontend/devserver/devserver.go @@ -49,6 +49,11 @@ func (d *DevWebServer) WindowReload() { func (d *DevWebServer) Run(ctx context.Context) error { d.ctx = ctx + assetdir, _ := ctx.Value("assetdir").(string) + d.server.Get("/wails/assetdir", func(fctx *fiber.Ctx) error { + return fctx.SendString(assetdir) + }) + d.server.Get("/wails/reload", func(fctx *fiber.Ctx) error { d.WindowReload() d.desktopFrontend.WindowReload() diff --git a/v2/internal/fs/fs.go b/v2/internal/fs/fs.go index 862e003cb..0f6fbb7ea 100644 --- a/v2/internal/fs/fs.go +++ b/v2/internal/fs/fs.go @@ -4,9 +4,11 @@ import ( "crypto/md5" "fmt" "io" + "io/fs" "os" "path/filepath" "runtime" + "strings" "unsafe" "github.com/leaanthony/slicer" @@ -394,3 +396,30 @@ func MoveDirExtended(src string, dst string, ignore []string) (err error) { return } + +func FindPathToFile(fsys fs.FS, file string) (string, error) { + stat, _ := fs.Stat(fsys, file) + if stat != nil { + return ".", nil + } + var indexFiles slicer.StringSlicer + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, file) { + indexFiles.Add(path) + } + return nil + }) + if err != nil { + return "", err + } + + if indexFiles.Length() > 1 { + return "", fmt.Errorf("multiple '%s' files found in assets", file) + } + + path, _ := filepath.Split(indexFiles.AsSlice()[0]) + return path, nil +}