Wails 是一个允许您使用 Go 和 Web 技术构建桌面应用程序的框架。它结合了 Go 语言的强大后端能力与现代 Web 界面的灵活性,帮助开发者快捷地创建轻量级、原生感强的跨平台桌面应用。

传统的桌面应用开发通常需要学习特定的 GUI 框架(如 Qt, Electron, WPF/WinForms 等),这对于 Web 开发者来说学习曲线陡峭。Electron 虽然解决了 Web 技术栈的问题,但其应用体积庞大、内存占用高,且集成了 Node.js 运行时,额外增加了依赖。Wails 则提供了一种优雅的解决方案:它使用原生 WebView 渲染界面,后端逻辑全部由 Go 语言编写,实现了轻量级、高性能和原生体验的桌面应用。


一、Wails 简介与核心优势

Wails 的核心理念是:用 Go 语言编写应用后端(业务逻辑),用 Web 前端技术(HTML, CSS, JavaScript)构建应用界面(UI)。它将 Go 程序和基于 Webview 的前端巧妙地结合在一起,实现两者之间的双向通信。

Wails 的核心优势:

  1. 原生 Webview 渲染:不捆绑 Chromium 运行时(像 Electron 那样),而是利用操作系统提供的原生 Webview 控件(如 Windows 上的 WebView2/EdgeHTML, macOS 上的 WebKit, Linux 上的 WebKitGTK/WebView2 )。
    • 体积小巧:最终应用程序包大小显著小于 Electron 应用。
    • 内存占用低:原生 Webview 通常比嵌入式 Chromium 更节省内存。
    • 原生体验:UI 渲染性能接近原生,集成了系统级功能。
  2. 高性能 Go 后端:所有业务逻辑都在 Go 运行时中执行,充分利用 Go 语言的并发优势和高性能特性。
  3. 双向通信:Go 后端可以直接调用前端 JavaScript 函数,前端 JavaScript 也可以直接调用 Go 后端方法,实现无缝交互。
  4. 跨平台:一次编写,多处运行,支持 Windows、macOS 和 Linux。
  5. 易于集成前端框架:支持 Vue, React, Angular, Svelte 等任何前端框架。
  6. 编译为单个可执行文件:部署简单,无需额外依赖 (除了原生 Webview,通常系统自带或易于安装)。

二、Wails 工作原理

Wails 的工作原理可以概括为以下几点:

  1. Webview 嵌入:Wails 创建一个 Go 语言进程,并在该进程中启动一个原生 Webview 控件。这个 Webview 控件负责渲染你的前端 Web 代码(HTML, CSS, JavaScript)。
  2. 文件服务:在应用程序启动时,Wails 会将你编译后的前端项目打包或作为静态资源嵌入到 Go 可执行文件中。Go 后端会运行一个小型文件服务器,将这些前端资源提供给 Webview 控件。
  3. JavaScript 绑定:Wails 在 Webview 的 JavaScript 全局对象上注入了一个 window.wails 对象(或其他名称),该对象包含了与 Go 后端通信的方法。
  4. Go 方法注册:Go 后端通过 Wails SDK 注册需要暴露给前端调用的 Go 方法。
  5. 通信桥接
    • JS 调用 Go:当前端 JavaScript 调用 window.wails.Call("YourGoMethod", ...args) 时,Wails 会将该调用请求序列化,通过内部的通信桥接(通常是基于 Webview 的原生通信机制,如 dom.bind 等)传递给 Go 后端。Go 后端解析请求,执行对应的 Go 方法,并将结果返回给前端 JS。
    • Go 调用 JS:Go 后端可以通过 Wails 的运行时 API runtime.EventsEmitruntime.Callback 直接向前端发送事件或调用 JS 函数。
  6. 最小化依赖:Go 应用编译成单一可执行文件,减少了外部依赖。唯一需要的系统依赖是对应平台的 WebView 运行时。

三、开发环境准备

3.1 安装 Go 语言

确保你的系统已安装 Go 1.18 或更高版本。

1
go version

3.2 安装 Wails CLI

Wails 提供了命令行工具 wails 来创建、运行和构建项目。

1
go install github.com/wailsapp/wails/v2/cmd/wails@latest

安装完成后,验证是否成功:

1
wails doctor

wails doctor 会检查你的系统环境是否满足 Wails 的开发和构建要求,并提示缺少哪些依赖。根据提示安装缺少的依赖(例如在 Windows 上安装 WebView2 Runtime 和 C++ Build Tools,在 Linux 上安装 WebKitGTK 及其开发库等)。

3.3 Node.js / NPM (可选,取决于你的前端技术栈)

如果你使用 Vue, React 等现代前端框架,可能需要安装 Node.js 和 npm/yarn 来管理和构建前端项目。

四、创建你的第一个 Wails 项目

使用 wails init 命令创建新项目:

1
wails init -n MyWailsApp -t vanilla
  • -n MyWailsApp:指定项目名称为 MyWailsApp
  • -t vanilla:指定前端模板为 vanilla (原生 JS/HTML/CSS)。Wails 也支持 vue, react, svelte, angular 等模板。

这会在当前目录创建一个名为 MyWailsApp 的文件夹,包含 Wails 项目的基本结构。

项目结构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyWailsApp/
├── wails.json # Wails 项目配置文件
├── main.go # Go 后端主入口文件
├── go.mod # Go 模块文件
├── frontend/ # 前端项目目录
│ ├── src/ # 前端源码
│ │ ├── main.js
│ │ └── style.css
│ │ └── index.html
│ └── package.json # 前端依赖管理 (如果使用 npm/yarn)
│ └── ... # 其他前端文件
├── build/ # 构建目录 (Wails 自动生成)
│ ├── appicon.png
│ └── ...
└── app.go # Go 应用逻辑文件 (Wails 自动生成)

五、开发流程

5.1 Go 后端逻辑 (app.go)

app.go 文件包含了你的 Go 应用程序的核心逻辑,它会作为前端可调用的方法被 Wails 自动绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"context"
"fmt"
)

// App struct
type App struct {
ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
return &App{}
}

// Startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, Go is awesome!", name)
}

// SumNumbers sums two numbers
func (a *App) SumNumbers(a, b int) int {
return a + b
}
  • App 结构体:定义了你的应用对象。
  • Startup(ctx context.Context):当应用启动时被调用,你可以保存 context 以便后续使用 Wails runtime 方法(如事件发送)。
  • Greet(name string) stringSumNumbers(a, b int) int:这些都是暴露给前端的 Go 方法。Wails 会自动将它们注册到前端 window.wails 对象上。注意:方法名首字母需大写才能被前端调用。

5.2 前端界面 (frontend/src/main.jsfrontend/src/index.html)

前端的 main.js 文件将通过 window.go.main.App.Greet 等方式调用 Go 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- frontend/src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Wails App</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="app">
<h1>Welcome to Wails!</h1>
<input id="nameInput" type="text" placeholder="Enter your name...">
<button onclick="greet()">Greet</button>
<p id="greetingOutput"></p>

<h2>Sum two numbers</h2>
<input id="num1Input" type="number" value="10">
<input id="num2Input" type="number" value="20">
<button onclick="sum()">Sum</button>
<p id="sumOutput"></p>
</div>
<script src="./main.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// frontend/src/main.js
import { main } from "../wailsjs/go/models"; // 导入Go的模型(类型定义)
import { App } from "../wailsjs/go/main"; // 导入Go后端方法

document.addEventListener('DOMContentLoaded', () => {
// 监听 Go 方法的调用
window.onload = function() {
console.log("Wails has loaded!");
};
});

async function greet() {
const nameInput = document.getElementById('nameInput');
const greetingOutput = document.getElementById('greetingOutput');
const name = nameInput.value;
if (name) {
// 调用 Go 后端的 App 结构体中的 Greet 方法
const result = await App.Greet(name);
greetingOutput.textContent = result;
} else {
greetingOutput.textContent = "Please enter a name.";
}
}

async function sum() {
const num1Input = document.getElementById('num1Input');
const num2Input = document.getElementById('num2Input');
const sumOutput = document.getElementById('sumOutput');
const num1 = parseInt(num1Input.value);
const num2 = parseInt(num2Input.value);

// 调用 Go 后端的 App 结构体中的 SumNumbers 方法
const result = await App.SumNumbers(num1, num2);
sumOutput.textContent = `Sum: ${result}`;
}

// 暴露出函数以便在 HTML 中通过 onclick 调用
window.greet = greet;
window.sum = sum;

注意:

  • ../wailsjs/go/main../wailsjs/go/models 是 Wails 自动生成的 Go 后端方法和类型定义的 JavaScript 绑定文件。这些文件在 wails devwails build 时会自动生成/更新。
  • 你需要将函数暴露出到 window 对象,才能在 index.htmlonclick 中直接引用。或者使用更现代的前端框架来管理事件。

5.3 运行应用程序

在项目根目录执行:

1
wails dev

wails dev 会启动一个开发服务器,自动编译 Go 代码,并在一个新窗口中打开你的应用。每次保存 Go 代码或前端代码时,它都会自动热重载,方便调试。

六、Wails 双向通信机制详解

Wails 提供强大的双向通信能力,是其核心亮点之一。

6.1 前端调用 Go (JS -> Go)

这是最常见的模式,前端通过 JavaScript 调用 Go 后端的逻辑。

  • 调用方式:通过 Wails 自动生成的 window.go.<packageName>.<StructName>.<MethodName>(...args)
    • 例子window.go.main.App.Greet("World") (如果你的 App 结构体在 main 包中)
    • 推荐方式 (JS Module):如上例,先 import { App } from "../wailsjs/go/main";,然后 App.Greet("World")
  • 参数类型:Go 方法可以接受基本类型、结构体、切片、Map 等作为参数。Wails 会自动进行 JSON 序列化/反序列化。
  • 返回值:Go 方法可以返回任何可序列化的 Go 类型。

6.2 Go 调用前端 (Go -> JS)

Go 后端可以通过 Wails Runtime API 向前端发送事件或执行 JS 代码。

6.2.1 发送事件 (推荐)

Go 后端向前端广播事件,前端监听事件并触发响应。这是更解耦、优雅的通信方式。

Go 代码 (app.go):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"context"
"fmt"
"time"

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

type App struct {
ctx context.Context
}
// ... Startup 方法省略 ...

// SendMessageToFrontend sends a message to the frontend every second
func (a *App) StartSendingMessages() {
go func() {
for i := 0; i < 5; i++ {
msg := fmt.Sprintf("Message from Go: %d", i)
// 发布事件
runtime.EventsEmit(a.ctx, "myMessage", msg) // "myMessage" 是事件名, msg 是数据
time.Sleep(time.Second)
}
runtime.EventsEmit(a.ctx, "myMessage", "Go has finished sending messages!")
}()
}

前端 JS (main.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ... (之前的代码)

document.addEventListener('DOMContentLoaded', () => {
// ... (之前的代码)

// 监听 Go 后端发送的事件
window.runtime.EventsOn("myMessage", (message) => {
console.log("Received from Go:", message);
const eventOutput = document.createElement('p');
eventOutput.textContent = `Event from Go: ${message}`;
document.getElementById('app').appendChild(eventOutput);
});

// 启动 Go 后端发送消息的函数
App.StartSendingMessages();
});
  • runtime.EventsEmit(ctx, eventName, data):在 Go 后端发送事件。
  • window.runtime.EventsOn(eventName, callback):在前端 JS 监听事件。

6.2.2 执行 JavaScript (慎用)

Go 后端可以执行任意的 JavaScript 代码。

Go 代码 (某个 Go 方法中):

1
runtime.ExecJS(a.ctx, "alert('Hello from Go backend in JavaScript!');")

前端 JS: 无需额外代码,直接执行。

考量

  • 优点:直接、灵活。
  • 缺点:耦合度高,不易维护,可能导致安全问题 (应避免执行不可信的 JS)。
  • 推荐:除非特定场景,尽量使用事件通信。

七、构建与部署

当你的应用开发完成后,可以使用 wails build 命令进行构建。

1
wails build

这会在 build/bin 目录下生成一个独立的、特定于当前操作系统的可执行文件。

常用构建选项:

  • wails build -r:构建 release 版本(优化、减小体积),默认包含调试信息。
  • wails build --clean:在构建前清理缓存。
  • wails build --upx:使用 UPX 压缩可执行文件(需要先安装 UPX)。
  • wails build --platform windows/amd64:交叉编译到指定平台。
  • wails build --platform windows/amd64,linux/amd64:交叉编译到多个平台。

注意事项:

  • Windows:确保系统中安装了 WebView2 Runtime (Edge Chromium)。Windows 10/11 通常预装;旧版本可能需要手动安装。
  • macOS:通常无需额外依赖。
  • Linux:依赖 WebKitGTK 或 WebView2。你需要确保目标系统安装了 webkit2gtk 或类似的包。例如在 Ubuntu/Debian 上:sudo apt install webkit2gtk-4.0

八、Wails 配置文件 (wails.json)

wails.json 文件是 Wails 项目的配置中心,你可以自定义应用名称、图标、窗口大小、Frontend 命令等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"$schema": "https://wails.io/schemas/wails.json",
"name": "MyWailsApp",
"outputfilename": "mywailsapp",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev": "npm run dev",
"frontend:dir": "frontend",
"wailsjsdir": "./frontend/wailsjs",
"author": {
"name": "Your Name",
"email": "you@example.com"
},
"info": {
"productName": "My Awesome Wails App"
},
"options": {
"bindings": {
"css": {
"output": ""
},
"typescript": {
"output": ""
}
},
"appicon": "build/appicon.png",
"devtools": {
"enabled": true
},
"window": {
"width": 1024,
"height": 768,
"resizable": true,
"frameless": false,
"sizefixed": false,
"fullscreen": false,
"alwaysOnTop": false,
"backgroundType": "opaque",
"minimisable": true,
"maximisable": true
}
}
}
  • frontend:install, frontend:build, frontend:dev:自定义前端项目的安装、构建和开发命令。如果你使用 npm, yarn, pnpm 或其他构建工具,可以在这里配置。
  • frontend:dir:前端项目源代码的目录。
  • wailsjsdir:Wails 自动生成的 JS 绑定文件的输出目录。

九、其他实用特性

  • 上下文菜单: Wails 允许你自定义右键上下文菜单。
  • 通知:支持系统级的通知。
  • Dialogs:文件选择、消息提示等系统原生对话框。
  • Dark Mode (深色模式):Wails 可以感知系统深色模式设置,方便前端适配。
  • 应用图标和构建设置:通过 wails.jsonbuild/ 目录进行配置。

十、总结

Wails 框架为 Go 开发者提供了一个强大而新颖的桌面应用开发体验。它巧妙地结合了 Go 的后端性能与 Web 的前端灵活性,同时避免了 Electron 的体积和内存开销。如果你是 Go 开发者,又希望利用现代 Web 技术构建跨平台的桌面应用,Wails 绝对是一个值得你投入学习和使用的优秀选择。

通过简洁的 API、高效的双向通信和轻量级的原生 Webview,Wails 使得创建美观、高性能的桌面应用变得前所未有的简单。开始你的 Wails 之旅,用 Go 语言和 Web 技术,探索桌面应用的无限可能吧!