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 +}