mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 22:13:36 +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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
"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"
|
"golang.org/x/net/html"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@ -18,35 +22,42 @@ It injects a websocket based IPC script into `index.html`.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BrowserAssetServer struct {
|
type BrowserAssetServer struct {
|
||||||
runtimeJS []byte
|
assets fs.FS
|
||||||
assetdir string
|
runtimeJS []byte
|
||||||
appOptions *options.App
|
logger *logger.Logger
|
||||||
|
fromDisk bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrowserAssetServer(assetdir string, bindingsJSON string, appOptions *options.App) (*BrowserAssetServer, error) {
|
func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*BrowserAssetServer, error) {
|
||||||
result := &BrowserAssetServer{
|
result := &BrowserAssetServer{}
|
||||||
assetdir: assetdir,
|
_logger := ctx.Value("logger")
|
||||||
appOptions: appOptions,
|
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
|
var buffer bytes.Buffer
|
||||||
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
||||||
buffer.Write(runtime.RuntimeDesktopJS)
|
buffer.Write(runtime.RuntimeDesktopJS)
|
||||||
result.runtimeJS = buffer.Bytes()
|
result.runtimeJS = buffer.Bytes()
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *BrowserAssetServer) loadFileFromDisk(filename string) ([]byte, error) {
|
func (d *BrowserAssetServer) LogDebug(message string, args ...interface{}) {
|
||||||
return os.ReadFile(filepath.Join(a.assetdir, filename))
|
if d.logger != nil {
|
||||||
|
d.logger.Debug("[BrowserAssetServer] "+message, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *BrowserAssetServer) processIndexHTML() ([]byte, error) {
|
func (a *BrowserAssetServer) processIndexHTML() ([]byte, error) {
|
||||||
indexHTML, err := a.loadFileFromDisk("index.html")
|
indexHTML, err := fs.ReadFile(a.assets, "index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -96,7 +107,13 @@ func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) {
|
|||||||
case "/wails/ipc.js":
|
case "/wails/ipc.js":
|
||||||
content = runtime.WebsocketIPC
|
content = runtime.WebsocketIPC
|
||||||
default:
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"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"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DesktopAssetServer struct {
|
type DesktopAssetServer struct {
|
||||||
assets debme.Debme
|
assets fs.FS
|
||||||
runtimeJS []byte
|
runtimeJS []byte
|
||||||
assetdir string
|
|
||||||
logger *logger.Logger
|
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{}
|
result := &DesktopAssetServer{}
|
||||||
|
|
||||||
_logger := ctx.Value("logger")
|
_logger := ctx.Value("logger")
|
||||||
@ -30,22 +26,18 @@ func NewDesktopAssetServer(ctx context.Context, assets embed.FS, bindingsJSON st
|
|||||||
result.logger = _logger.(*logger.Logger)
|
result.logger = _logger.(*logger.Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
_assetdir := ctx.Value("assetdir")
|
var err error
|
||||||
if _assetdir != nil {
|
result.assets, result.fromDisk, err = prepareAssetsForServing(ctx, "DesktopAssetServer", assets)
|
||||||
result.assetdir = _assetdir.(string)
|
if err != nil {
|
||||||
absdir, err := filepath.Abs(result.assetdir)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result.LogDebug("Loading assets from: %s", absdir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
||||||
buffer.Write(runtime.RuntimeDesktopJS)
|
buffer.Write(runtime.RuntimeDesktopJS)
|
||||||
result.runtimeJS = buffer.Bytes()
|
result.runtimeJS = buffer.Bytes()
|
||||||
err := result.init(assets)
|
|
||||||
return result, err
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DesktopAssetServer) LogDebug(message string, args ...interface{}) {
|
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) {
|
func (a *DesktopAssetServer) processIndexHTML() ([]byte, error) {
|
||||||
indexHTML, err := a.ReadFile("index.html")
|
indexHTML, err := fs.ReadFile(a.assets, "index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -146,7 +83,13 @@ func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) {
|
|||||||
case "/wails/ipc.js":
|
case "/wails/ipc.js":
|
||||||
content = runtime.DesktopIPC
|
content = runtime.DesktopIPC
|
||||||
default:
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
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"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -100,25 +99,14 @@ func (d *DevWebServer) Run(ctx context.Context) error {
|
|||||||
_devServerURL := ctx.Value("devserverurl")
|
_devServerURL := ctx.Value("devserverurl")
|
||||||
if _devServerURL == "http://localhost:34115" {
|
if _devServerURL == "http://localhost:34115" {
|
||||||
// Setup internal dev server
|
// Setup internal dev server
|
||||||
_assetdir := ctx.Value("assetdir")
|
bindingsJSON, err := d.appBindings.ToJSON()
|
||||||
if _assetdir == nil {
|
if err != nil {
|
||||||
return fmt.Errorf("no assetdir provided")
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if _assetdir != nil {
|
|
||||||
assetdir := _assetdir.(string)
|
d.assetServer, err = assetserver.NewBrowserAssetServer(ctx, d.appoptions.Assets, bindingsJSON)
|
||||||
bindingsJSON, err := d.appBindings.ToJSON()
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal(err)
|
||||||
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.server.Get("*", d.loadAsset)
|
d.server.Get("*", d.loadAsset)
|
||||||
|
@ -2,11 +2,11 @@ package options
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"io/fs"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ type App struct {
|
|||||||
HideWindowOnClose bool
|
HideWindowOnClose bool
|
||||||
AlwaysOnTop bool
|
AlwaysOnTop bool
|
||||||
RGBA *RGBA
|
RGBA *RGBA
|
||||||
Assets embed.FS
|
Assets fs.FS
|
||||||
Menu *menu.Menu
|
Menu *menu.Menu
|
||||||
Logger logger.Logger `json:"-"`
|
Logger logger.Logger `json:"-"`
|
||||||
LogLevel logger.LogLevel
|
LogLevel logger.LogLevel
|
||||||
|
Loading…
Reference in New Issue
Block a user