mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 08:10:56 +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:
parent
6fcd4b7bd4
commit
131a8f421d
@ -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")
|
||||
|
@ -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
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "{{.ProjectName}}",
|
||||
"outputfilename": "{{.BinaryName}}",
|
||||
"assetdir": "frontend/dist",
|
||||
"frontend:install": "npm install",
|
||||
"frontend:build": "npm run build",
|
||||
"author": {
|
||||
|
@ -36,7 +36,6 @@
|
||||
<working_directory value="$PROJECT_DIR$"/>
|
||||
<go_parameters value="-gcflags "all=-N -l" -tags dev -o {{.PathToDesktopBinary}}"/>
|
||||
<useCustomBuildTags value="true"/>
|
||||
<parameters value="-assetdir {{.AssetDir}}"/>
|
||||
<envs>
|
||||
<env name="CGO_ENABLED" value=""{{.CGOEnabled}}""/>
|
||||
</envs>
|
||||
|
@ -9,11 +9,7 @@
|
||||
"program": "${workspaceFolder}/{{.PathToDesktopBinary}}",
|
||||
"preLaunchTask": "build",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {},
|
||||
"args": [
|
||||
"-assetdir",
|
||||
"{{.AssetDir}}"
|
||||
]
|
||||
"env": {}
|
||||
}
|
||||
]
|
||||
}
|
@ -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 = ""
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "{{.ProjectName}}",
|
||||
"outputfilename": "{{.BinaryName}}",
|
||||
"assetdir": "frontend/dist",
|
||||
"frontend:install": "npm install",
|
||||
"frontend:build": "npm run build",
|
||||
"wailsjsdir": "./frontend",
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "{{.ProjectName}}",
|
||||
"outputfilename": "{{.BinaryName}}",
|
||||
"assetdir": "frontend/src",
|
||||
"wailsjsdir": "./frontend",
|
||||
"author": {
|
||||
"name": "{{.AuthorName}}",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user