5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 19:50:15 +08:00

Reorder startup sequence

Add plugin scripts to asset server
Add InjectJS support in plugins
This commit is contained in:
Lea Anthony 2023-03-24 08:43:00 +11:00
parent bf86b0d9c1
commit 5949e305ea
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
12 changed files with 209 additions and 76 deletions

View File

@ -2,8 +2,11 @@ package assetserver
import (
"bytes"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"strings"
"golang.org/x/net/html"
@ -42,6 +45,9 @@ type AssetServer struct {
// Use http based runtime
runtimeHandler RuntimeHandler
// plugin scripts
pluginScripts map[string]string
assetServerWebView
}
@ -89,6 +95,16 @@ func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) {
d.runtimeHandler = handler
}
func (d *AssetServer) AddPluginScript(pluginName string, script string) {
if d.pluginScripts == nil {
d.pluginScripts = make(map[string]string)
}
pluginName = strings.ReplaceAll(pluginName, "/", "_")
pluginName = html.EscapeString(pluginName)
pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
d.pluginScripts[pluginScriptName] = script
}
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if isWebSocket(req) {
// Forward WebSockets to the distinct websocket handler if it exists
@ -149,6 +165,11 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
d.writeBlob(rw, path, content)
default:
// Check if this is a plugin script
if script, ok := d.pluginScripts[path]; ok {
d.writeBlob(rw, path, []byte(script))
return
}
d.handler.ServeHTTP(rw, req)
}
}
@ -174,6 +195,13 @@ func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
return nil, err
}
// Inject plugins
for scriptName := range d.pluginScripts {
if err := insertScriptInHead(htmlNode, scriptName); err != nil {
return nil, err
}
}
var buffer bytes.Buffer
err = html.Render(&buffer, htmlNode)
if err != nil {

View File

@ -108,3 +108,75 @@ This attribute specifies which javascript event should trigger the action. The d
<button data-wml-event="hover-box" data-wml-trigger="mouseover">Hover over me!</button>
```
## Plugins
Plugins are a way to extend the functionality of your Wails application.
### Creating a plugin
Plugins are standard Go structure that adhere to the following interface:
```go
type Plugin interface {
Name() string
Init(*application.App) error
Shutdown()
CallableByJS() []string
InjectJS() string
}
```
The `Name()` method returns the name of the plugin. This is used for logging purposes.
The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
parameter is the application that the plugin is being loaded into. Any errors will prevent
the application from starting.
The `Shutdown()` method is called when the application is shutting down.
The `CallableByJS()` method returns a list of exported functions that can be called from
the frontend. These method names must exactly match the names of the methods exported
by the plugin.
The `InjectJS()` method returns JavaScript that should be injected into all windows as they are created. This is useful for adding custom JavaScript functions that complement the plugin.
### Tips
#### Enums
In Go, enums are often defined as a type and a set of constants. For example:
```go
type MyEnum int
const (
MyEnumOne MyEnum = iota
MyEnumTwo
MyEnumThree
)
```
Due to incompatibility between Go and JavaScript, custom types cannot be used in this way. The best strategy is to use a type alias for float64:
```go
type MyEnum = float64
const (
MyEnumOne MyEnum = iota
MyEnumTwo
MyEnumThree
)
```
In Javascript, you can then use the following:
```js
const MyEnum = {
MyEnumOne: 0,
MyEnumTwo: 1,
MyEnumThree: 2
}
```
- Why use `float64`? Can't we use `int`?
- Because JavaScript doesn't have a concept of `int`. Everything is a `number`, which translates to `float64` in Go. There are also restrictions on casting types in Go's reflection package, which means using `int` doesn't work.

View File

@ -5,6 +5,7 @@ import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"github.com/wailsapp/wails/v3/pkg/application"
)
@ -32,6 +33,10 @@ func (r *Plugin) CallableByJS() []string {
}
}
func (r *Plugin) InjectJS() string {
return ""
}
// ---------------- Plugin Methods ----------------
type Hashes struct {

View File

@ -38,6 +38,7 @@ func New(appOptions Options) *App {
log: logger.New(appOptions.Logger.CustomLoggers...),
contextMenus: make(map[string]*Menu),
}
globalApplication = result
if !appOptions.Logger.Silent {
result.log.AddOutput(&logger.Console{})
@ -60,7 +61,24 @@ func New(appOptions Options) *App {
srv.UseRuntimeHandler(NewMessageProcessor())
result.assets = srv
globalApplication = result
result.bindings, err = NewBindings(appOptions.Bind)
if err != nil {
println("Fatal error in application initialisation: ", err.Error())
os.Exit(1)
}
err = result.bindings.AddPlugins(appOptions.Plugins)
if err != nil {
println("Fatal error in application initialisation: ", err.Error())
os.Exit(1)
}
result.plugins = NewPluginManager(appOptions.Plugins, srv)
err = result.plugins.Init()
if err != nil {
println("Fatal error in application initialisation: ", err.Error())
os.Exit(1)
}
return result
}
@ -318,20 +336,6 @@ func (a *App) Run() error {
}
}()
var err error
a.bindings, err = NewBindings(a.options.Bind)
if err != nil {
return err
}
a.plugins = NewPluginManager(a.options.Plugins)
err = a.plugins.Init()
if err != nil {
return err
}
a.bindings.AddPlugins(a.options.Plugins)
// run windows
for _, window := range a.windows {
go window.run()
@ -348,7 +352,7 @@ func (a *App) Run() error {
// set the application Icon
a.impl.setIcon(a.options.Icon)
err = a.impl.run()
err := a.impl.run()
if err != nil {
return err
}

View File

@ -2,10 +2,11 @@ package application
import (
"fmt"
"github.com/samber/lo"
"reflect"
"runtime"
"strings"
"github.com/samber/lo"
)
type CallOptions struct {
@ -174,7 +175,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct", name)
}
return nil, fmt.Errorf("not a pointer to a struct")

View File

@ -1,5 +1,7 @@
package application
import "github.com/wailsapp/wails/v2/pkg/assetserver"
type Plugin interface {
Name() string
Init(app *App) error
@ -10,13 +12,14 @@ type Plugin interface {
type PluginManager struct {
plugins map[string]Plugin
assetServer *assetserver.AssetServer
}
func NewPluginManager(plugins map[string]Plugin) *PluginManager {
func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager {
result := &PluginManager{
plugins: plugins,
assetServer: assetServer,
}
globalApplication.OnWindowCreation(result.onWindowCreation)
return result
}
@ -28,7 +31,7 @@ func (p *PluginManager) Init() error {
}
injectJS := plugin.InjectJS()
if injectJS != "" {
p.assetServer.AddPluginScript(plugin.Name(), injectJS)
}
globalApplication.info("Plugin '%s' initialised", plugin.Name())
}
@ -41,12 +44,3 @@ func (p *PluginManager) Shutdown() {
globalApplication.info("Plugin '%s' shutdown", plugin.Name())
}
}
func (p *PluginManager) onWindowCreation(window *WebviewWindow) {
for _, plugin := range p.plugins {
injectJS := plugin.InjectJS()
if injectJS != "" {
window.ExecJS(injectJS)
}
}
}

View File

@ -1,29 +0,0 @@
# Plugins
Plugins are a way to extend the functionality of your Wails application.
## Creating a plugin
Plugins are standard Go structure that adhere to the following interface:
```go
type Plugin interface {
Name() string
Init(*application.App) error
Shutdown()
CallableByJS() []string
}
```
The `Name()` method returns the name of the plugin. This is used for logging purposes.
The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
parameter is the application that the plugin is being loaded into. Any errors will prevent
the application from starting.
The `Shutdown()` method is called when the application is shutting down.
The `CallableByJS()` method returns a list of exported functions that can be called from
the frontend. These method names must exactly match the names of the methods exported
by the plugin.

View File

@ -1,3 +0,0 @@
# TODO
- [ ] Add InjectCSS() to the plugin API?

View File

@ -33,6 +33,10 @@ func (p *Plugin) CallableByJS() []string {
}
}
func (p *Plugin) InjectJS() string {
return ""
}
// ---------------- Plugin Methods ----------------
func (p *Plugin) OpenURL(url string) error {

View File

@ -2,11 +2,12 @@ package kvstore
import (
"encoding/json"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/logger"
"io"
"os"
"sync"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/logger"
)
type KeyValueStore struct {
@ -18,6 +19,10 @@ type KeyValueStore struct {
app *application.App
}
func (kvs *KeyValueStore) InjectJS() string {
return ""
}
type Config struct {
Filename string
AutoSave bool
@ -67,6 +72,10 @@ func (kvs *KeyValueStore) CallableByJS() []string {
}
}
func (p *Plugin) InjectJS() string {
return ""
}
// ---------------- Plugin Methods ----------------
func (kvs *KeyValueStore) open(filename string) (err error) {

View File

@ -1,18 +1,23 @@
package log
import (
_ "embed"
"fmt"
"github.com/wailsapp/wails/v3/pkg/application"
"io"
"os"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed plugin.js
var pluginJS string
// ---------------- Plugin Setup ----------------
// This is the main plugin struct. It can be named anything you like.
// It must implement the application.Plugin interface.
// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops.
type LogLevel int
type LogLevel = float64
const (
Trace LogLevel = iota + 1
@ -55,6 +60,7 @@ func NewPluginWithConfig(config *Config) *Plugin {
}
return &Plugin{
config: config,
level: config.Level,
}
}
@ -88,9 +94,14 @@ func (p *Plugin) CallableByJS() []string {
"Warning",
"Error",
"Fatal",
"SetLevel",
}
}
func (p *Plugin) InjectJS() string {
return pluginJS
}
// ---------------- Plugin Methods ----------------
// Plugin methods are just normal Go methods. You can add as many as you like.
// The only requirement is that they are exported (start with a capital letter).
@ -98,7 +109,7 @@ func (p *Plugin) CallableByJS() []string {
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *Plugin) write(prefix string, level LogLevel, message string, args ...any) {
if level >= p.config.Level {
if level >= p.level {
if !p.config.DisablePrefix {
message = prefix + " " + message
}
@ -134,5 +145,8 @@ func (p *Plugin) Fatal(message string, args ...any) {
}
func (p *Plugin) SetLevel(newLevel LogLevel) {
if newLevel == 0 {
newLevel = Debug
}
p.level = newLevel
}

View File

@ -8,7 +8,7 @@
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Trace(input, ...args) {
function Trace(input, ...args) {
return wails.Plugin("log", "Trace", input, ...args);
}
@ -19,7 +19,7 @@ export function Trace(input, ...args) {
* @returns {Promise<void|Error>}
*/
export function Debug(input, ...args) {
function Debug(input, ...args) {
return wails.Plugin("log", "Debug", input, ...args);
}
@ -29,7 +29,7 @@ export function Debug(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Info(input, ...args) {
function Info(input, ...args) {
return wails.Plugin("log", "Info", input, ...args);
}
@ -39,7 +39,7 @@ export function Info(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Warning(input, ...args) {
function Warning(input, ...args) {
return wails.Plugin("log", "Warning", input, ...args);
}
@ -49,7 +49,7 @@ export function Warning(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Error(input, ...args) {
function Error(input, ...args) {
return wails.Plugin("log", "Error", input, ...args);
}
@ -59,6 +59,40 @@ export function Error(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Fatal(input, ...args) {
function Fatal(input, ...args) {
return wails.Plugin("log", "Fatal", input, ...args);
}
/**
* SetLevel sets the logging level
* @param level {Level} The log level to set
* @returns {Promise<void>}
*/
function SetLevel(level) {
return wails.Plugin("log", "SetLevel", level);
}
/**
* Log Level.
* @readonly
* @enum {number}
*/
let Level = {
Trace: 1,
Debug: 2,
Info: 3,
Warning: 4,
Error: 5,
Fatal: 6,
};
window.Logger = {
Trace,
Debug,
Info,
Warning,
Error,
Fatal,
SetLevel,
Level,
}