它是如何工作的?
Wails 应用程序是一个带有一个 webkit 前端的标准的 Go 应用程序。 应用程序的 Go 部分由应用程序代码和一个运行时库组成, 该库提供了许多有用的操作,例如控制应用程序窗口。 前端是一个 webkit 窗口,将显示前端资源。 前端还可以使用运行时库的 JavaScript 版本。 最后,可以将 Go 方法绑定到前端,这些将显示为可以调用的 JavaScript 方法,就像它们是原生 JavaScript 方法一样。
主应用程序
概述
主应用程序由对 wails.Run()
的调用组成。 它接受描述应用程序窗口大小、窗口标题、要使用的资源等应用程序配置。 基本应用程序可能如下所示:
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := &App{}
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
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
:
//go:embed all:frontend/dist
var assets embed.FS
启动时,Wails 将遍历嵌入的文件,寻找包含的 index.html
。 所有其他资源将相对于该目录加载。
由于可用于生产的二进制文件使用包含在 embed.FS
中的文件,因此应用程序不需要附带任何外部文件。
在开发模式下使用 wails dev
命令,资产从磁盘加载,任何更改都会导致“实时重新加载”。 资产的位置将从 embed.FS
推断。
更多细节可以在 应用开发指南 中找到。
应用程序生命周期回调
在即将加载前端 index.html
之前,会对 应用启动回调 中提供的函数进行调用。 一个标准的 Go context 被传递给这个方法。 调用运行时需要此 context ,因此标准模式是在此方法中保存对它的引用。 在应用程序关闭之前,以同样的方式调用 应用退出回调,并再次使用上下文 在应用程序关闭之前,以同样的方式调用 应用退出回调,并再次使用上下文 当前端加载完 index.html
中所有资源时,还有一个 前端 Dom 加载完成回调 ,相当于 JavaScript 中的 body onload
事件。 还可以通过设置 应用关闭前回调 选项来控制窗口关闭(或应用程序退出)事件。
方法绑定
Bind
选项是 Wails 应用程序中最重要的参数选项之一。 它指定向前端暴露哪些结构体方法。 想想传统 web 应用程序中的 "Controllers" 之类的结构 。 当应用程序启动时,它会检查 Bind
字段中列出的结构体实例, 确定哪些方法是公开的(以大写字母开头),并生成前端可以调用的这些方法的 JavaScript 版本。
Wails 要求您传入结构体的 实例 以使其正确绑定
在此示例中,我们创建一个新的 App
实例,然后将此实例添加到 wails.Run
中的 Bind
选项:
package main
import (
"embed"
"log"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := &App{}
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
}
type App struct {
ctx context.Context
}
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
您可以绑定任意数量的结构体。 只需确保创建它的实例并将其传递给 Bind
:
//...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
&mystruct1{},
&mystruct2{},
},
})
当您运行 wails dev
(或 wails generate module
)时,将生成一个前端模块,其中包含以下内容:
- 所有绑定方法的 JavaScript 绑定
- 所有绑定方法的 TypeScript 声明
- 绑定方法用作输入或输出的所有 Go 结构的 TypeScript 声明
这使得使用相同的强类型数据结构从前端调用 Go 代码变得异常简单。
前端
概述
前端是由 webkit 渲染的文件集合。 这就合二为一的浏览器和网络服务器。 您可以使用的框架或库1几乎没有限制。 前端和 Go 代码之间的主要交互点是:
- 调用绑定的 Go 方法
- 调用运行时方法
调用绑定的 Go 方法
当您使用 wails dev
运行应用程序时,它将自动在名为 wailsjs/go
的目录中为您的结构体生成 JavaScript 绑定(您也可以通过运行 wails generate module
来执行此操作)。 生成的文件反映了应用程序中的包名称。 在上面的例子中,我们绑定了有公开方法 Greet
的 app
。 这将导致生成以下文件:
wailsjs
└─go
└─main
├─App.d.ts
└─App.js
在这里我们可以看到有一个 main
包,其中包含绑定 App
结构体的 JavaScript 绑定,以及这些方法的 TypeScript 声明文件。 要从我们的前端调用 Greet
,我们只需导入该方法并像普通的 JavaScript 函数一样调用它:
// ...
import { Greet } from "../wailsjs/go/main/App";
function doGreeting(name) {
Greet(name).then((result) => {
// Do something with result
});
}
TypeScript 声明文件为您提供了绑定方法的正确类型:
export function Greet(arg1: string): Promise<string>;
生成的方法返回一个 Promise 成功的调用将导致 Go 调用的第一个返回值被传递给 resolve
处理程序。 不成功的调用是当 Go 方法的第二个返回值具有错误类型时,将错误实例传递回调用者。 这通过 reject
处理程序传回的。 在上面的示例中,Greet
只返回一个 string
,因此 JavaScript 调用永远不会 reject - 除非将无效数据传递给它。
所有数据类型都在 Go 和 JavaScript 之间正确转换。 包括结构体。 如果您从 Go 调用返回一个结构体,它将作为 JavaScript 类返回到您的前端。
结构体字段 必须 具有有效的 json
标签,以包含在生成的 TypeScript 中。
目前不支持嵌套匿名结构体。
也可以将结构体发送回 Go。 作为期望的结构体的参数传递的任何 JavaScript map/class 都将转换为该结构体类型。 为了使这个过程更容易,在 开发
模式下,会生成一个 TypeScript 模块,声明绑定方法中使用的所有结构体类型。 使用此模块,可以构建原生 JavaScript 对象并将其发送到 Go 代码。
还支持在其签名中使用结构的 Go 方法。 绑定方法(作为参数或返回类型)指定的所有 Go 结构体都将作为 Go 代码包装器模块的一部分自动生成 TypeScript 版本。 使用这些,可以在 Go 和 JavaScript 之间共享相同的数据模型。
示例:我们更新 Greet
方法以接受一个 Person
而不是字符串:
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)
}
wailsjs/go/main/App.js
文件仍将包含以下代码:
export function Greet(arg1) {
return window["go"]["main"]["App"]["Greet"](arg1);
}
但是 wailsjs/go/main/App.d.ts
文件将使用以下代码进行更新:
import { main } from "../models";
export function Greet(arg1: main.Person): Promise<string>;
正如我们所见,“main”命名空间是从一个新的“models.ts”文件中导入的。 该文件包含我们绑定方法使用的所有结构体定义。 在此示例中,这是一个 Person
结构。 如果我们查看 models.ts
,我们可以看到模型是如何定义的:
export namespace main {
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 作为前端构建配置的一部分,您就可以通过以下方式使用这些模型:
import { Greet } from "../wailsjs/go/main/App";
import { main } from "../wailsjs/go/models";
function generate() {
let person = new main.Person();
person.name = "Peter";
person.age = 27;
Greet(person).then((result) => {
console.log(result);
});
}
生成的绑定和 TypeScript 模型的结合构成了一个强大的开发环境。
有关绑定的更多信息,请参见 应用开发指南 的 绑定方法 部分。
调用运行时方法
JavaScript 运行时位于window.runtime
并包含许多方法来执行各种任务,例如发出事件或执行日志记录操作:
window.runtime.EventsEmit("my-event", 1);
更多关于 JS 运行时的细节可以在 运行时参考 中找到。
- 有一小部分库使用了 WebView 中不支持的功能。 对于这种情况,通常有替代方案和解决方法。↩