如果你用过 Wails v2 写桌面应用,大概率遇到过这几个别扭点:配置全塞进一个 wails.Run()、想拿窗口句柄得绕、想做多窗口更麻烦、构建过程像个黑盒。2026-08-02,团队在历经三年半重写后发布了 Wails v3 Beta。它不是小版本迭代,而是一次架构级重构:用显式的 App / Window / Service 对象模型,替换掉那套包罗万象的隐式 API,并原生支持多窗口、静态分析生成前端绑定、以及可读可改的 Taskfile 构建流程。
本文带你从零跑通一个 v3 多窗口 Demo,并讲清楚 v2 到 v3 到底变了什么、迁移时该避开哪些坑。所有命令与 API 均来自官方文档(v3.wails.io)与发布博客,Beta 阶段字段若有微调请以官方模板为准。
背景与痛点:为什么需要 v3
v2 的应用层 API 本质上只有一个函数------wails.Run(options),把窗口、绑定、菜单、资产业务全塞进一个配置结构里。它带来三个长期被吐槽的痛点:
- 应用入口是隐式的 :主窗口句柄被框架藏着,想精细控制生命周期只能依赖单独的 Runtime API,而 Runtime API 又必须传入一个
context.Context才能工作,调用链路很绕。 - 多窗口是"绕出来的":v2 没有一等公民的多窗口模型,做编辑器 + 检查器这类多窗口界面要靠各种 workaround。
- 构建是黑盒 :
wails build内部悄悄串联了编译 Go、生成绑定、装前端依赖、打包等步骤,想定制或排查都很难。
v3 正是冲着这三点来的。下面这张对比图先建立整体印象。

图1:v2 的单一隐式入口(wails.Run + context.Context)对比 v3 的显式对象模型(App / Window / Service),绿色侧窗口与绑定都是一等公民。
环境准备:Go 1.25+ 与 wails3 CLI
v3 对运行环境有两个硬门槛,先确认到位再动手:
- Go 1.25 及以上。这是官方 Beta 文档明确要求的版本。
- 平台原生 WebView 依赖 :Windows 用系统自带的 WebView2;macOS 用 WKWebView;Linux 需要 GTK4 + WebKitGTK 6.0 ,缺少时
wails3 setup会直接报错提示。
安装 v3 专属的 CLI(注意模块路径带 /v3/ 且命令叫 wails3,与 v2 的 wails 分开,互不冲突):
bash
# 安装 v3 CLI(始终取最新 Beta)
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
运行环境体检向导,自动检查并提示缺失依赖
wails3 setup
若后续遇到诡异问题,用 doctor 收集诊断信息再提 issue
wails3 doctor
wails3 setup 会逐项检查本机工具链(Go、WebView 依赖、打包工具等),Linux 上若缺 GTK4/WebKitGTK 会给出具体安装提示,比 v2 时代"编译到一半才报错"友好很多。
五分钟上手:初始化与目录结构
确认 CLI 就绪后,初始化一个项目。-t 指定前端模板,-n 指定项目名:
bash
# 用 vanilla 模板初始化(也可选 react / vue / svelte / vanilla-js / react-js)
wails3 init -n myapp -t vanilla
列出所有可用模板
wails3 init -l
进入项目并启动开发模式:Go 改动自动重编译,前端改动热更新
cd myapp
wails3 dev
典型工程结构大致如下(具体以 wails3 init 生成结果为准):
text
myapp/
├── main.go # 应用入口:创建 App、注册 Service、开窗口
├── Taskfile.yml # 构建流程定义(可读、可改)
├── frontend/ # 前端资源(HTML/CSS/JS 或框架)
│ └── dist/ # 生产构建产物
├── bindings/ # 由 Go Service 静态分析生成的 TS 绑定
└── bin/ # wails3 build 产物(原生可执行文件)
跑起来后你会得到一个用系统原生 WebView 渲染的窗口,前端随便用什么框架都行------React、Vue、Svelte 或直接原生 JS。
核心变化一:显式对象模型(App / Window / Service)
v3 最大的心智转变,是从"配置一切的函数"变成"可以主动操作的对象"。下面这段 main.go 是 v3 的骨架(基于官方文档整理,Beta 阶段字段可能微调,请以 wails3 init 生成的模板为准):
go
package main
import (
"embed"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
//go:embed all:frontend/dist
var assets embed.FS
// GreetService 是一个普通的 Go 结构体。
// v3 会静态扫描其导出的方法,并生成对应的 TypeScript 绑定。
type GreetService struct{}
func (g *GreetService) Greet(name string) string {
return "Hello, " + name + "!"
}
func main() {
// 1) 显式创建应用实例,而不是把配置塞进 wails.Run()
app := application.New(application.Options{
Name: "MultiWindowDemo",
Description: "Wails v3 多窗口示例",
Services: []application.Service{
application.NewService(&GreetService{}), // 2) 注册 Go 服务
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
// 3) 用对象 API 直接创建窗口,不再需要隐式 context
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "主窗口",
Width: 1000,
Height: 618,
URL: "/",
})
// 4) 直接对窗口对象注册事件,调用链清晰可读
mainWindow.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Quit()
})
app.Run()
}
关键点:应用、窗口都成了可以 new、可以持有、可以监听事件 的对象。Runtime 调用不再依赖隐式 context.Context 在闭包里传来传去,测试和拆分都更顺手。
核心变化二:原生多窗口与独立生命周期
多窗口在 v3 里是一等公民。每个窗口都是独立对象,有各自的生命周期,互不依赖 Context 传递------这正是 v2 时代最痛的点。给上面的 Demo 再加一个"检查器"窗口:
go
func main() {
app := application.New(application.Options{Name: "MultiWindowDemo"})
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "主窗口",
URL: "/",
})
mainWindow.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Quit()
})
// 第二个窗口:独立生命周期,独立 URL,互不干扰
inspector := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "检查器",
Width: 480,
Height: 640,
URL: "/inspector.html",
})
_ = inspector // 可持有引用,按需调用其方法或绑定事件
app.Run()
}
两个窗口各自拥有独立生命周期,关闭主窗口触发 app.Quit(),检查器窗口不受影响。下图把这种"App 持有多个 Window、每个 Window 绑定独立 WebView"的关系画清楚。

图2:v3 中 App 实例持有多个 Window 对象,每个 Window 拥有独立生命周期与独立 WebView;Service 在 App 层注册,事件(如 WindowClosing)可驱动 App 级行为(如 Quit)。
核心变化三:静态分析生成 TypeScript 绑定
v2 生成绑定要先编译出一个二进制,再对二进制做反射------典型的"鸡生蛋"循环。v3 改成对 Go 源码做静态分析(AST),直接生成 TypeScript 绑定,还能保留注释与参数名,前端调用体验更自然:
ts
// bindings/ 下按服务名生成,约等于从 Go 方法"翻译"过来的类型安全封装
import { GreetService } from "../bindings/GreetService";
const result = await GreetService.Greet("Wails");
console.log(result); // Hello, Wails!
意图与预期:你在 Go 侧改了 Greet 的签名,重新构建时绑定会同步更新,前端调用处若不匹配会编译报错,而不是运行时才炸。Service 还可以自带前端资源,为未来的插件化生态铺路。
构建与发布:Taskfile 驱动的透明流程
v3 把构建过程从黑盒改成了可读的 Taskfile.yml(类似 Makefile)。默认一键构建会依次完成:Go 代码优化编译 → 生成 TS 绑定 → 前端生产构建(压缩)→ 产出对应平台的原生可执行文件(默认在 bin/):
bash
# 默认流程:编译 + 生成绑定 + 前端压缩 + 产物落 bin/
wails3 build
想自定义?直接改 Taskfile.yml 里对应的 task,比如调整图标生成、压缩等级、打包参数,每一步都看得见、改得动。整个流水线如图3所示。

图3:wails3 build 的透明流水线------Go 编译、AST 绑定生成、前端生产构建串联执行,最终产出原生可执行文件;任意环节都可在 Taskfile.yml 中定制。
顺带一提,v3 还把同一套 Go 代码通过实验性的移动支持直接编译到 iOS / Android(零改 Go 代码),命令是 wails3 task ios:run 与 wails3 task android:run,但这部分尚属实验性,不在桌面兼容性保证范围内。
避坑指南:v2→v3 迁移与 Beta 风险
真要上手,下面几条能少走弯路:
- v2→v3 是"移植"不是"升级" :官方明确说不要指望改个 import 路径就成功。应用/窗口生命周期模型、用 Service 取代 Context 绑定、前端绑定重新生成,都要逐个有意识地迁移。官方提供了 v2→v3 迁移指南,含功能对照表与测试清单。
- 生产环境先留着 v2:在新应用真正就绪前,让 v2 版本继续留在生产里,别急着整体切换。
- Linux 先看依赖 :GTK4 + WebKitGTK 6.0 没装,
wails3 setup会直接提示,别硬跑wails3 dev才发现问题。 - Beta 阶段的现实预期 :API 公开行为仍可能变动;Beta 期间官方文档只维护英文版;v2 仍是稳定版,继续收修复,短期不会被抛弃。遇到可复现 bug,按官方要求附上
wails3 doctor输出和最小复现。 - 性能数据别当普适保证:官方文档宣称的"约 15MB 二进制 vs Electron 150MB、约 10MB 基线内存 vs 100MB+、小于 0.5s 启动 vs 2--3s"属于厂商基准,实际数字随项目规模、依赖、平台差异很大,请勿当作通用承诺。
总结与延伸
Wails v3 Beta 用"显式对象模型 + 原生多窗口 + 静态分析绑定 + Taskfile 透明构建"四件套,精准回应了 v2 时代最常被吐槽的几个点。如果你之前因为"不支持多窗口""Runtime API 太绕"而对 Wails 望而却步,现在正是重新审视它的好时机。它依旧保持核心优势:一套 Go 代码覆盖 Windows / macOS / Linux,调用系统原生 WebView,不打包整个 Chromium。