用 Wails + Go + Vue3 开发桌面软件,聊聊踩过的坑

用 Wails + Go + Vue3 开发工具,聊聊踩过的坑

背景交代:用Wails开发桌面软件,覆盖图片、音频、视频、电子书、文档、数据六大类。聊聊技术选型和实现过程中遇到的一些问题,顺便给考虑用 Wails 做桌面应用的同学一些参考。


为什么选 Wails 而不是 Electron

这个问题几乎是第一个面对的选择。Electron 生态更成熟,社区大,VS Code、Figma、Slack 都在用。但对我来说有几个问题:

打包体积:一个 Electron 应用的最小体积大概是 80-150MB,因为要把 Chromium 和 Node.js 都打进去。Wails 打包出来的 .exe 通常在 15-30MB 左右,差距挺大的。

内存占用:Electron 应用的内存基线比较高,一个简单的工具应用跑起来就要 150-200MB 内存,Wails 因为用系统 WebView,基线低得多。

技术栈:后端我已经确定用 Go,Wails 的架构天然适合,前端可以用 Vue3/React,跟 Web 开发体验一致。

当然 Wails 也有代价:WebView 在不同 Windows 版本上的表现有细微差异(依赖系统的 WebView2),某些 CSS 特性在老 Windows 上可能有问题。不过 WebView2 现在已经随 Windows 11 内置了,Windows 10 的覆盖率也在提升。


项目结构

bash 复制代码
ConvertET/
├── main.go              # Wails 入口
├── app.go               # 前端调用的 RPC 接口
├── backend/
│   ├── converters/      # 各类格式转换器
│   │   ├── image/
│   │   ├── audio/
│   │   ├── video/
│   │   ├── ebook/
│   │   ├── document/
│   │   └── data/
│   ├── services/        # 业务逻辑
│   ├── models/          # 数据结构定义
│   └── utils/           # 工具函数
└── frontend/            # Vue3 前端
    └── src/
        ├── components/
        ├── stores/      # Pinia 状态管理
        └── locales/     # i18n 国际化文件

Wails 的前后端通信模型:Go 里面暴露的方法,在 JS 里可以直接作为异步函数调用,Wails 帮你处理了序列化和 IPC。比如:

go 复制代码
// Go 端定义
func (a *App) StartConversion(request models.ConversionRequest) *models.Result {
    return a.appService.StartConversion(request)
}
javascript 复制代码
// Vue 里直接调用
import { StartConversion } from '../wailsjs/go/main/App'

const result = await StartConversion({
  inputPath: filePath,
  targetFormat: 'mp4',
  options: { quality: 'high' }
})

这个 DX(开发者体验)是真的不错,省去了定义 IPC channel、事件处理这些 Electron 里的样板代码。


图片转换:为什么不用 ImageMagick

图片转换这块,我最初的想法是调 ImageMagick,功能强大,格式支持广。但有个问题:用户需要单独安装 ImageMagick 并配置环境变量,这对普通用户来说门槛很高。

最后选择了纯 Go 实现:

go 复制代码
import (
    "image/jpeg"
    "image/png"
    "golang.org/x/image/bmp"
    "golang.org/x/image/webp"
    "github.com/disintegration/imaging"
)
  • image/jpegimage/pngimage/gif:标准库
  • golang.org/x/image/bmpgolang.org/x/image/webp:官方扩展库
  • github.com/disintegration/imaging:提供高质量缩放(Lanczos 算法)

这套组合覆盖了 JPEG/PNG/BMP/WEBP/GIF 五种格式,不依赖任何外部可执行文件,打包进二进制直接用。

唯一的遗憾是 WEBP 编码:golang.org/x/image/webp 只支持解码,不支持编码。所以目前转到 WEBP 格式的输出实际上是 PNG。这是 Go 标准库的历史局限,正在考虑引入 libwebp 或者找纯 Go 实现的方案。


有个坑:文件扩展名和实际格式不符

用户传进来的文件,扩展名未必和实际格式一致。我见过把 PNG 文件命名成 .jpg 的,也见过把 JPEG 改成 .png 的。

所以图片加载时,我加了 magic bytes 检测:

go 复制代码
func detectImageFormat(r io.Reader) string {
    header := make([]byte, 12)
    n, _ := r.Read(header)
    if n < 2 {
        return ""
    }
    switch {
    case header[0] == 0xFF && header[1] == 0xD8:
        return "jpeg"
    case header[0] == 0x89 && header[1] == 0x50:
        return "png"
    case header[0] == 0x47 && header[1] == 0x49:
        return "gif"
    case header[0] == 0x42 && header[1] == 0x4D:
        return "bmp"
    case header[0] == 0x52 && header[1] == 0x49:
        return "webp"  // RIFF 容器
    }
    return ""
}

读文件头的前几字节判断格式,优先级高于扩展名,避免因为错误的文件扩展名导致解码失败。


进度回调的设计

格式转换可能要跑几秒到几分钟,UI 需要实时显示进度。Wails 提供了事件机制:

go 复制代码
// 在转换器里调用回调
type ProgressCallback func(progress int, message string)

// 回调函数通过 Wails runtime 发送事件到前端
callback := func(progress int, message string) {
    runtime.EventsEmit(ctx, "conversion:progress", map[string]interface{}{
        "taskID":   taskID,
        "progress": progress,
        "message":  message,
    })
}

前端监听事件:

javascript 复制代码
import { EventsOn } from '../wailsjs/runtime'

EventsOn('conversion:progress', (data) => {
  task.progress = data.progress
  task.statusMessage = data.message
})

这个模式用下来挺好,进度条更新流畅,没有明显的 UI 卡顿。


国际化:18 种语言怎么维护

前端用 vue-i18n,语言文件放在 locales/ 目录下,每种语言一个 JSON 文件。系统语言自动检测这块,在 stores/app.ts 里用 navigator.language 做了一个语言代码到 locale key 的映射。

维护 18 种语言翻译是个体力活。目前英文是精确翻译,其他语言用了机器翻译初版 + 部分人工校对。欢迎有多语言能力的朋友来帮忙。


最后

Wails 对于做 Go 生态的桌面工具来说是个很好的框架,前后端分离清晰,WebView 渲染效果好(Vuetify 这类 Material Design 框架跑得很顺)。社区没有 Electron 大,但文档比较完善,遇到问题去 GitHub Issues 基本都能找到答案。

相关推荐
上进小菜猪3 小时前
把 KES 集群交给 Kubernetes 的九十天:一个 DBA 的试用实录
后端
两点王爷4 小时前
PostgreSQL 常用 SQL 语句与 GIS 相关函数详解
数据库·后端
考虑考虑4 小时前
Springboot环境变量占位符语法
spring boot·后端·spring
步行cgn6 小时前
Spring 基于 XML 的自动装配:byName 详解
java·后端·spring
RISCV_Explorer8 小时前
RISC-V启动与运行时规范机制解析——BRS约定、HSM多核启动与设备树要求
后端·risc-v
星栈独行8 小时前
ADK-Rust 是什么?Rust 生态新一代 AI Agent 开发套件
人工智能·后端·rust
JavaGuide8 小时前
对标 MinIO!全新一代分布式文件系统正式发布
前端·后端
用户8356290780519 小时前
使用 Python 将 DOCX 转换为 Markdown
后端·python
IT_陈寒9 小时前
Vue computed属性这个坑,我居然踩了三次才爬出来
前端·人工智能·后端
孙启超10 小时前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust