用 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 基本都能找到答案。

相关推荐
好家伙VCC2 小时前
区块链双向支付通道实战:从签名到结算
java·后端·区块链·asp.net
我登哥MVP3 小时前
Spring Boot 从“会用”到“精通”:参数解析原理
java·spring boot·后端·spring·servlet·maven·intellij-idea
JustHappy3 小时前
古法编程秘籍(五):什么是进程和线程?从软件到 CPU 的一次完整旅程
前端·后端·代码规范
BLSxiaopanlaile3 小时前
关于常见 map的一些比较探究
后端
花大师4 小时前
基于深度学习的鼠标轨迹真实性检测系统
后端
小江的记录本4 小时前
【Spring全家桶】Spring Cloud 2023.0.x:微服务核心理论、CAP/BASE定理(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·spring·spring cloud·微服务·面试
我登哥MVP5 小时前
Spring Boot 从“会用”到“精通”:Model-Map原理
java·spring boot·后端·spring·servlet·maven·mybatis
㳺三才人子5 小时前
初探 Flask-WTF
后端·python·flask·html5