5
0
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:
stffabi 2021-12-03 10:25:38 +01:00
parent f3b2f6ab76
commit 6fcd4b7bd4
9 changed files with 196 additions and 135 deletions

View File

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

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

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

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
//go:build production
package assetserver
func (a *DesktopAssetServer) ReadFile(filename string) ([]byte, error) {
return a.assets.ReadFile(filename)
}

View File

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

View File

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