5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 17:52:29 +08:00

[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.
This commit is contained in:
stffabi 2021-12-06 08:49:12 +01:00
parent 6fcd4b7bd4
commit 131a8f421d
16 changed files with 142 additions and 178 deletions

View File

@ -83,7 +83,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
flags := defaultDevFlags() flags := defaultDevFlags()
command.StringFlag("ldflags", "optional ldflags", &flags.ldflags) command.StringFlag("ldflags", "optional ldflags", &flags.ldflags)
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &flags.compilerCommand) 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.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go", &flags.extensions)
command.BoolFlag("browser", "Open application in browser", &flags.openBrowser) command.BoolFlag("browser", "Open application in browser", &flags.openBrowser)
command.BoolFlag("noreload", "Disable reload on asset change", &flags.noReload) 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 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 != "" { if flags.assetDir == "" && projectConfig.AssetDirectory != "" {
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) projectConfig.AssetDirectory = filepath.ToSlash(flags.assetDir)
} }
flags.assetDir, err = filepath.Abs(flags.assetDir) if flags.assetDir != "" {
if err != nil { flags.assetDir, err = filepath.Abs(flags.assetDir)
return nil, err if err != nil {
return nil, err
}
} }
if flags.devServerURL == defaultDevServerURL && projectConfig.DevServerURL != defaultDevServerURL && projectConfig.DevServerURL != "" { 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 interval := time.Duration(flags.debounceMS) * time.Millisecond
timer := time.NewTimer(interval) timer := time.NewTimer(interval)
rebuild := false rebuild := false
reload := false assetDir := ""
changedPaths := map[string]struct{}{}
for quit == false { for quit == false {
//reload := false //reload := false
select { select {
@ -519,12 +518,13 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
// Check for file writes // Check for file writes
if item.Op&fsnotify.Write == fsnotify.Write { if item.Op&fsnotify.Write == fsnotify.Write {
// Ignore directories // Ignore directories
if fs.DirExists(item.Name) { itemName := item.Name
if fs.DirExists(itemName) {
continue continue
} }
// Iterate all file patterns // Iterate all file patterns
ext := filepath.Ext(item.Name) ext := filepath.Ext(itemName)
if ext != "" { if ext != "" {
ext = ext[1:] ext = ext[1:]
if _, exists := extensionsThatTriggerARebuild[ext]; exists { if _, exists := extensionsThatTriggerARebuild[ext]; exists {
@ -534,9 +534,8 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
} }
} }
if strings.HasPrefix(item.Name, flags.assetDir) { changedPaths[filepath.Dir(itemName)] = struct{}{}
reload = true
}
timer.Reset(interval) timer.Reset(interval)
} }
// Check for new directories // Check for new directories
@ -568,6 +567,34 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
debugBinaryProcess = newBinaryProcess 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 { if reload {
reload = false reload = false
_, err = http.Get("http://localhost:34115/wails/reload") _, err = http.Get("http://localhost:34115/wails/reload")

View File

@ -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. - 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`. 2. Update `README.md`.
3. Edit `wails.json` and ensure all fields are correct, especially: 3. Edit `wails.json` and ensure all fields are correct, especially:
- `assetdir` - path to your assets
- `wailsjsdir` - path to generate wailsjs modules - `wailsjsdir` - path to generate wailsjs modules
- `frontend:install` - The command to install your frontend dependencies - `frontend:install` - The command to install your frontend dependencies
- `frontend:build` - The command to build your frontend - `frontend:build` - The command to build your frontend

View File

@ -1,7 +1,6 @@
{ {
"name": "{{.ProjectName}}", "name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}", "outputfilename": "{{.BinaryName}}",
"assetdir": "frontend/dist",
"frontend:install": "npm install", "frontend:install": "npm install",
"frontend:build": "npm run build", "frontend:build": "npm run build",
"author": { "author": {

View File

@ -36,7 +36,6 @@
<working_directory value="$PROJECT_DIR$"/> <working_directory value="$PROJECT_DIR$"/>
<go_parameters value="-gcflags &quot;all=-N -l&quot; -tags dev -o {{.PathToDesktopBinary}}"/> <go_parameters value="-gcflags &quot;all=-N -l&quot; -tags dev -o {{.PathToDesktopBinary}}"/>
<useCustomBuildTags value="true"/> <useCustomBuildTags value="true"/>
<parameters value="-assetdir {{.AssetDir}}"/>
<envs> <envs>
<env name="CGO_ENABLED" value="&quot;{{.CGOEnabled}}&quot;"/> <env name="CGO_ENABLED" value="&quot;{{.CGOEnabled}}&quot;"/>
</envs> </envs>

View File

@ -9,11 +9,7 @@
"program": "${workspaceFolder}/{{.PathToDesktopBinary}}", "program": "${workspaceFolder}/{{.PathToDesktopBinary}}",
"preLaunchTask": "build", "preLaunchTask": "build",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"env": {}, "env": {}
"args": [
"-assetdir",
"{{.AssetDir}}"
]
} }
] ]
} }

View File

@ -42,7 +42,6 @@ type Data struct {
AuthorNameAndEmail string AuthorNameAndEmail string
WailsDirectory string WailsDirectory string
GoSDKPath string GoSDKPath string
AssetDir string
WindowsFlags string WindowsFlags string
CGOEnabled string CGOEnabled string
OutputFile string OutputFile string
@ -60,7 +59,6 @@ type Options struct {
InitGit bool InitGit bool
AuthorName string AuthorName string
AuthorEmail string AuthorEmail string
AssetDir string
IDE string IDE string
ProjectNameFilename string // The project name but as a valid filename ProjectNameFilename string // The project name but as a valid filename
WailsVersion string WailsVersion string
@ -261,7 +259,6 @@ func Install(options *Options) (bool, *Template, error) {
AuthorName: options.AuthorName, AuthorName: options.AuthorName,
WailsVersion: options.WailsVersion, WailsVersion: options.WailsVersion,
GoSDKPath: options.GoSDKPath, GoSDKPath: options.GoSDKPath,
AssetDir: options.AssetDir,
} }
// Create a formatted name and email combo. // Create a formatted name and email combo.
@ -408,22 +405,6 @@ func installIDEFiles(o ideOptions) error {
binaryName += ".exe" 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.PathToDesktopBinary = filepath.ToSlash(filepath.Join("build", "bin", binaryName))
o.options.WindowsFlags = "" o.options.WindowsFlags = ""

View File

@ -1,7 +1,6 @@
{ {
"name": "{{.ProjectName}}", "name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}", "outputfilename": "{{.BinaryName}}",
"assetdir": "frontend/dist",
"frontend:install": "npm install", "frontend:install": "npm install",
"frontend:build": "npm run build", "frontend:build": "npm run build",
"wailsjsdir": "./frontend", "wailsjsdir": "./frontend",

View File

@ -1,7 +1,6 @@
{ {
"name": "{{.ProjectName}}", "name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}", "outputfilename": "{{.BinaryName}}",
"assetdir": "frontend/src",
"wailsjsdir": "./frontend", "wailsjsdir": "./frontend",
"author": { "author": {
"name": "{{.AuthorName}}", "name": "{{.AuthorName}}",

View File

@ -5,7 +5,10 @@ package appng
import ( import (
"context" "context"
"embed"
"flag" "flag"
"fmt"
iofs "io/fs"
"os" "os"
"path/filepath" "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 != "" { if devServerURL != "" {
ctx = context.WithValue(ctx, "devserverurl", devServerURL) ctx = context.WithValue(ctx, "devserverurl", devServerURL)
} }
if assetdir != "" {
ctx = context.WithValue(ctx, "assetdir", assetdir)
}
if loglevel != "" { if loglevel != "" {
level, err := pkglogger.StringToLogLevel(loglevel) level, err := pkglogger.StringToLogLevel(loglevel)
@ -203,3 +224,32 @@ func generateBindings(bindings *binding.Bindings) error {
return nil 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
}

View File

@ -12,7 +12,6 @@ import (
"github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"golang.org/x/net/html" "golang.org/x/net/html"
"path/filepath"
) )
/* /*
@ -26,7 +25,6 @@ type BrowserAssetServer struct {
assets fs.FS assets fs.FS
runtimeJS []byte runtimeJS []byte
logger *logger.Logger logger *logger.Logger
fromDisk bool
} }
func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*BrowserAssetServer, error) { 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 var err error
result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "BrowserAssetServer", assets) result.assets, err = prepareAssetsForServing(assets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -108,11 +106,7 @@ func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) {
content = runtime.WebsocketIPC content = runtime.WebsocketIPC
default: default:
filename = strings.TrimPrefix(filename, "/") filename = strings.TrimPrefix(filename, "/")
fromDisk := "" a.LogDebug("Loading file: %s", filename)
if a.fromDisk {
fromDisk = " (disk)"
}
a.LogDebug("Loading file: %s%s", filename, fromDisk)
content, err = fs.ReadFile(a.assets, filename) content, err = fs.ReadFile(a.assets, filename)
} }
if err != nil { if err != nil {

View File

@ -1,64 +1,25 @@
package assetserver package assetserver
import ( import (
"context" iofs "io/fs"
"fmt"
"io/fs"
"path" "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) { func prepareAssetsForServing(assets iofs.FS) (iofs.FS, 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 { 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 { 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 { if err != nil {
return nil, false, err return nil, err
} }
return assets, false, nil return assets, 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
} }

View File

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

View File

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

View File

@ -15,7 +15,6 @@ type DesktopAssetServer struct {
assets fs.FS assets fs.FS
runtimeJS []byte runtimeJS []byte
logger *logger.Logger logger *logger.Logger
fromDisk bool
} }
func NewDesktopAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*DesktopAssetServer, error) { 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 var err error
result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "DesktopAssetServer", assets) result.assets, err = prepareAssetsForServing(assets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -84,11 +83,7 @@ func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) {
content = runtime.DesktopIPC content = runtime.DesktopIPC
default: default:
filename = strings.TrimPrefix(filename, "/") filename = strings.TrimPrefix(filename, "/")
fromDisk := "" a.LogDebug("Loading file: %s", filename)
if a.fromDisk {
fromDisk = " (disk)"
}
a.LogDebug("Loading file: %s%s", filename, fromDisk)
content, err = fs.ReadFile(a.assets, filename) content, err = fs.ReadFile(a.assets, filename)
} }
if err != nil { if err != nil {

View File

@ -49,6 +49,11 @@ func (d *DevWebServer) WindowReload() {
func (d *DevWebServer) Run(ctx context.Context) error { func (d *DevWebServer) Run(ctx context.Context) error {
d.ctx = ctx 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.server.Get("/wails/reload", func(fctx *fiber.Ctx) error {
d.WindowReload() d.WindowReload()
d.desktopFrontend.WindowReload() d.desktopFrontend.WindowReload()

View File

@ -4,9 +4,11 @@ import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"unsafe" "unsafe"
"github.com/leaanthony/slicer" "github.com/leaanthony/slicer"
@ -394,3 +396,30 @@ func MoveDirExtended(src string, dst string, ignore []string) (err error) {
return 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
}