跳到主要内容
版本:下个版本 🚧

它是如何工作的?

Wails 应用程序是一个带有一个 webkit 前端的标准的 Go 应用程序。 应用程序的 Go 部分由应用程序代码和一个运行时库组成, 该库提供了许多有用的操作,例如控制应用程序窗口。 前端是一个 webkit 窗口,将显示前端资源。 前端还可以使用运行时库的 Javascript 版本。 最后,可以将 Go 方法绑定到前端,这些将显示为可以调用的 Javascript 方法,就像它们是原生 Javascript 方法一样。

主应用程序

概述

主应用程序由对wails.Run()的调用组成. 它接受描述应用程序窗口大小、窗口标题、要使用的资源等应用程序配置。基本应用程序可能如下所示:

main.go
package main

import (
"embed"
"log"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

//go:embed frontend/dist
var assets embed.FS

func main() {

app := &App{}

err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}


type App struct {
ctx context.Context
}

func (b *App) startup(ctx context.Context) {
b.ctx = ctx
}

func (b *App) shutdown(ctx context.Context) {}

func (b *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}

选项概要

此示例设置了以下选项:

  • Title - 应该出现在窗口标题栏中的文本
  • Width&Height- 窗口的尺寸
  • Assets - 应用程序的前端资源
  • OnStartup - 创建窗口并即将开始加载前端资源时的回调
  • OnShutdown - 应用程序即将退出时的回调
  • Bind - 我们希望向前端暴露的一部分结构体实例

完整的应用程序参数选项列表可以在参数选项中找到。

资源

Assets 选项是必须的,因为您不能拥有没有前端资源的 Wails 应用程序。这些资源可以是您希望在 Web 应用程序中找到的任何文件 - html、js、css、svg、png 等。 不需要生成资源包 - 纯文件即可。当应用程序启动时,它将尝试从您的资源中加载index.html,并且那时起前端基本上将作为浏览器工作。值得注意的是embed.FS对文件所在的位置没有要求。嵌入路径很可能使用了相对于您的主应用程序代码的嵌套目录,例如 frontend/dist

main.go
//go:embed frontend/dist
var assets embed.FS

启动时,Wails 将遍历嵌入的文件,寻找包含的index.html。所有其他资源将相对于该目录加载。

由于可用于生产的二进制文件使用包含在embed.FS中的文件,因此应用程序不需要附带任何外部文件。

当使用wails dev命令在“开发”模式下,资源从磁盘加载,任何更改都会导致“实时重新加载”。资源的位置需要使用-assetdir传递给wails dev命令,并且很可能与嵌入路径相同。 希望将来我们可以从embed.FS本身计算出来。

更多细节可以在应用程序开发指南中找到。

应用程序生命周期回调

在即将加载前端index.html之前,对 应用启动回调 中提供的函数进行调用。一个标准的 Go 上下文被传递给这个方法。 调用运行时需要此上下文,因此标准方式是保存此时对它的引用。在应用程序关闭之前,再次使用上下文以同样的方式调用 应用退出回调。当前端完成加载index.html中所有资源时,还有一个 前端 Dom 加载完成回调 ,相当于 Javascript 中的body onload事件。还可以通过设置 关闭应用程序之前回调 选项来控制窗口关闭(或应用程序退出)事件。

方法绑定

Bind选项是 Wails 应用程序中最重要的参数选项之一。它指定向前端暴露哪些结构方法。当应用程序启动时,它会检查 Bind 中列出的结构实例, 确定哪些方法是公开的(以大写字母开头),并将生成前端可以调用的那些方法的 Javascript 版本。

注意

Wails 要求您传入结构的 实例 才能正确绑定它

在此示例中,我们创建一个新App实例,然后将此实例添加到wails.Run中的Bind选项:

main.go
package main

import (
"embed"
"log"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

//go:embed frontend/dist
var assets embed.FS

func main() {

app := &App{}

err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}


type App struct {
ctx context.Context
}

func (b *App) startup(ctx context.Context) {
b.ctx = ctx
}

func (b *App) shutdown(ctx context.Context) {}

func (b *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}

您可以绑定任意数量的结构体。只需确保创建它的一个实例并将其传递给 Bind

...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
&mystruct1{},
&mystruct2{},
},
})
...

这些方法位于前端 window.go.<packagename>.<struct>.<method>。在上面的例子中,我们绑定 app,它有一个公开方法 Greet。这可以通过在 Javascript 中用 window.go.main.App.Greet调用。这些方法返回一个 Promise。成功的调用将导致 Go 调用的第一个返回值被传递给 resolve 处理程序。一个不成功的调用是将一个 Go 方法的第二个错误类型返回值通过reject传递回调用者。在上面的例子中,Greet 只返回一个字符串,所以 Javascript 调用永远不会reject - 除非将无效数据传递给它。

所有数据类型都在 Go 和 Javascript 之间正确转换。包括结构体。如果您从 Go 调用返回一个结构体,它将作为 Javascript Map 返回到您的前端。 注意:如果您想使用结构体,您必须为您的结构体字段定义json 标签!

注意

目前不支持嵌套匿名结构体。

也可以将结构体发送回 Go。任何作为期望结构的参数传递的 Javascript Map 都将转换为该结构类型。 为了使这个过程更容易,在 开发模式下,会生成一个 TypeScript 模块,定义绑定方法中使用的所有结构类型。使用此模块,可以构建原生 Javascript 对象并将其发送到 Go 代码。

关于绑定的更多信息可以在应用程序开发指南绑定方法一节中找到。

前端

概述

前端是由 webkit 渲染的文件集合。这就像浏览器和网络服务器合二为一。您可以使用的框架或库1几乎没有限制。前端和 Go 代码之间的主要交互点是:

  • 调用绑定的 Go 方法
  • 调用运行时方法

调用绑定的 Go 方法

所有绑定的 Go 方法都可以在window.go.<package>.<struct>.<method>调用. 如上一节所述,这些方法返回一个 Promise, 其中成功调用将值返回给 resolve 处理程序,错误将值返回给 reject 处理程序。

mycode.js
 window.go.main.App.Greet("Bill").then((result) => {
console.log("The greeting is: " + result);
})

开发模式下运行应用程序时,会生成一个 javascript 模块,该模块用 JSDoc 注释包装这些方法。这确实有助于开发,尤其是因为大多数 IDE 将处理 JSDoc 以提供代码完成和类型提示。该模块名为go 并在wailsjsdir标志指定的目录中生成。在这个模块中有一个bindings.js的文件,其中包含这些包装器。对于上面的示例,该文件包含以下代码:

bindings.js
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: string
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;

支持结构体

还额外支持在其签名中使用结构的 Go 方法。所有由绑定方法指定的 Go 结构体(作为参数或返回类型)都将自动生成 Typescript 版本作为 Go 代码包装器模块的一部分。 使用这些,可以在 Go 和 Javascript 之间共享相同的数据模型。这些模型与 JSDoc 注释一致,支持 IDE 代码自动完成。

示例:我们更新我们的Gree方法以接受一个Person而不是字符串:

main.go
type Person struct {
Name string `json:"name"`
Age uint8 `json:"age"`
Address *Address `json:"address"`
}

type Address struct {
Street string `json:"street"`
Postcode string `json:"postcode"`
}

func (a *App) Greet(p Person) string {
return fmt.Sprintf("Hello %s (Age: %d)!", p.Name, p.Age)
}

我们的bindings.js文件现已更新以反映更改:

bindings.js
const go = {
main: {
App: {
/**
* Greet
* @param {Person} arg1 - Go Type: main.Person
* @returns {Promise<string>} - Go Type: string
*/
Greet: (arg1) => {
return window.go.main.App.Greet(arg1);
},
},
},
};
export default go;

此外bindings.js,还有一个名为models.ts的文件. 这包含我们 Go 结构体的 TypeScript 形式:

models.ts
export class Address {
street: string;
postcode: string;

static createFrom(source: any = {}) {
return new Address(source);
}

constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.street = source["street"];
this.postcode = source["postcode"];
}
}
export class Person {
name: string;
age: number;
address?: Address;

static createFrom(source: any = {}) {
return new Person(source);
}

constructor(source: any = {}) {
if ("string" === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.age = source["age"];
this.address = this.convertValues(source["address"], Address);
}

convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map((elem) => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}

只要您将 TypeScript 作为前端构建配置的一部分,您就可以通过以下方式使用这些模型:

mycode.js
import go from "./wailsjs/go/bindings";
import { Person } from "./wailsjs/go/models";

let name = "";

function greet(name) {
let p = new Person();
p.name = name;
p.age = 42;
go.main.App.Greet(p).then((result) => {
console.log(result);
});
}

JSDoc 和 TypeScript 生成模型的组合构成了一个强大的开发环境。

调用运行时方法

Javascript 运行时位于window.runtime并包含许多方法来执行各种任务,例如发出事件或执行日志记录操作:

mycode.js
window.runtime.EventsEmit("my-event", 1);

可以在运行时参考中找到有关 JS 运行时的更多详细信息。


  1. 有一小部分库使用了 WebView 中不支持的功能。对于这种情况,通常有替代方案和解决方法。