Wails 入门:用 Go 写桌面应用

这篇要解决的问题

写 Go 的团队迟早会遇到一个需求:给后端服务配一个桌面客户端,或者把内部工具做成双击就能跑的程序。这时候摆在你面前的通常是 Electron——它带一整个 Chromium 运行时,产物上百 MB、内存占用大;或者是 Tauri——同样是系统 WebView 方案,但后端语言是 Rust,Go 团队得为它再学一门语言。Wails 是第三条路:前端照常写 HTML/CSS/JS(或任意框架),后端直接用 Go,二者通过方法绑定互相调用。这篇按”装起来、跑通一个窗口、看懂它和 Electron/Tauri 的本质差异、判断要不要用”的顺序走一遍。写作时 v2 线的最新版是 2.9.2(2024 年 11 月初发布),v3 还在 alpha 阶段,本文全部基于 v2。

安装与第一个窗口

Wails 对环境的要求是 Go、Node.js,以及对应平台自带的 WebView:Windows 10/11 需要 WebView2 运行时(新版系统通常已内置),macOS 无额外要求,Linux 需要 webkit2gtk 的开发包。Go 与 Node 的最低版本要求以 wails.io 安装页为准。装 CLI 和初始化项目:

# 安装 Wails 命令行工具
go install github.com/wailsapp/wails/v2/cmd/wails@latest

1. 体检:检查各平台依赖是否齐了,缺什么它会告诉你装什么
wails doctor

1. 用 vue-ts 模板建项目;也支持 react、svelte、vanilla 等模板
wails init -n demo -t vue-ts

1. 开发模式:起 Vite 热更新 + Go 后端,改前端代码浏览器直接刷新
wails dev

wails build 会把前端构建产物和 Go 二进制打包成一个可执行文件,输出在 build/bin 下。定性地说,这个产物是十几 MB 的量级,而同等功能的 Electron 应用普遍上百 MB——差的这部分就是没有打包 Chromium 的成本。具体数字随应用内容浮动,以你自己构建的结果为准。

Go 侧与前端怎么接上

模板生成的 main.go 长这样,核心是三件事:嵌入前端资产、声明生命周期回调、把 Go 对象绑定给前端:

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

type App struct {
    ctx context.Context
}

// OnStartup 拿到运行时上下文,之后才能用窗口、事件等能力
func (a *App) startup(ctx context.Context) { a.ctx = ctx }

// 任何导出方法都会暴露给前端,参数和返回值自动做 JSON 序列化
func (a *App) Greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    app := NewApp()
    err := wails.Run(&options.App{
        Title:  "demo",
        Width:  800,
        Height: 600,
        AssetServer: &assetserver.Options{
            Assets: assets, // 页面请求都由这个内嵌资产服务响应
        },
        OnStartup: app.startup,
        Bind:      []interface{}{app}, // 绑定后前端才能调 Greet
    })
    if err != nil {
        log.Fatal(err)
    }
}

前端侧不用手写调用代码,wails dev 会自动生成 wailsjs/go/main/App.js 绑定模块,import 进来就是类型齐全的函数:

import { Greet } from '../wailsjs/go/main/App';

// 看起来是本地函数调用,实际跨了 JS 到 Go 的一次绑定调用
const result = await Greet('Wails');

反模式点名:把业务逻辑写在前端 JS 里、Go 只当文件读写工具。这样等于放弃 Go 的类型和并发能力,客户端代码也没法复用服务端逻辑。合理的分界是:状态管理和交互留前端,数据加工、系统调用、网络请求放 Go 侧,前端只消费绑定的方法。

除了方法绑定,双向还有事件通道:Go 侧任何时刻都能向前端广播事件,长任务的进度推送就靠它,而不是让前端轮询:

// Go 侧(import "github.com/wailsapp/wails/v2/pkg/runtime"):
// 处理完一段就发一条事件,载荷跟在事件名后面
runtime.EventsEmit(a.ctx, "task:progress", done, total)

// 前端侧:EventsOn 订阅同名事件
import { EventsOn } from '../wailsjs/runtime';
EventsOn("task:progress", (done, total) => updateBar(done, total));

v2 还内置了系统对话框、菜单、剪贴板等常用桌面能力的封装,多数工具型应用不用碰 CGO。要知道的能力边界是:v2 是单窗口模型,一个进程一个主窗口,多窗口是 v3 才补上的重点能力——如果产品形态必须多窗口,现在只能选 Electron/Tauri 或等 v3 稳定。

与 Electron/Tauri 的思路差异

维度 Electron Tauri Wails
页面渲染载体 自带 Chromium,跨平台渲染一致 系统 WebView 系统 WebView
后端语言 Node.js Rust Go
产物体积 大(打包整个运行时) 小 小(十几 MB 量级)
渲染一致性 完全可控 随系统 WebView 而异 随系统 WebView 而异
团队上手成本 低(全 JS 生态) 需 Rust 背景 低(Go 背景即可)

真正的取舍在”渲染一致性”这一行:Electron 用体积换来了所有平台同一版本的 Chromium,页面不会因为客户机器的 WebView 版本旧而样式错乱;Wails 和 Tauri 换来了小体积和原生语言后端,代价是要接受各家系统 WebView 的行为差异。工具型界面(表单、列表、配置面板)对此基本无感;如果你要做重度依赖最新 CSS 特性的界面,需要先评估目标用户的系统版本。

什么时候选它

三种场景值得直接选 Wails:团队主力是 Go、不想为桌面端引入第二门后端语言;做的是内网工具、运维面板、数据看板这类”轻界面 + 重后端”应用;在意安装包体积和内存占用。反过来两种情况要慎重:一是界面要求像素级一致的复杂渲染(先实测 WebView 兼容性),二是想现在就上 v3——它还在 alpha,API 未定型,生产项目请留在 v2。诚实地说,Wails 的社区规模小于 Electron 和 Tauri,遇到偏门问题的检索成本更高,这点要有预期。

延伸阅读:Go 侧代码的基础语法见《第一个 Go 程序》;绑定方法里跑后台任务时用到的并发原语见《Go 并发基础》;Greet 方法背后 Go 标准库的分工见《Go标准库详解》。

版本核实(以官方文档为准):写作时 Wails v2 线最新版为 2.9.2(2024 年 11 月初发布),v3 处于 alpha 阶段未发布正式版;本文命令与 API(wails doctor/init/dev/build、embed 资产嵌入、OnStartup、Bind、自动生成的 wailsjs 绑定)均为 v2 已发布行为,细节以 wails.io 当前文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇