mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 22:31:06 +08:00
[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.
This commit is contained in:
parent
f3b2f6ab76
commit
6fcd4b7bd4
@ -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
|
||||
|
64
v2/internal/frontend/assetserver/assetserver_common.go
Normal file
64
v2/internal/frontend/assetserver/assetserver_common.go
Normal file
@ -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
|
||||
}
|
57
v2/internal/frontend/assetserver/assetserver_common_dev.go
Normal file
57
v2/internal/frontend/assetserver/assetserver_common_dev.go
Normal file
@ -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
|
||||
}
|
12
v2/internal/frontend/assetserver/assetserver_common_nodev.go
Normal file
12
v2/internal/frontend/assetserver/assetserver_common_nodev.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
//go:build production
|
||||
|
||||
package assetserver
|
||||
|
||||
func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) {
|
||||
return a.assets.ReadFile(filename)
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user