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

相关推荐
大阳光男孩4 小时前
Spring Boot 整合 Debezium 实现 MySQL 增量数据监听(嵌入式版)
spring boot·后端·mysql
皮皮林5514 小时前
ThreadLocal 不香了?ScopedValue才是王道?
后端
X-⃢_⃢-X4 小时前
一、第一阶段:认识 Spring Boot
java·spring boot·后端
前端工作日常5 小时前
我学习到的Java程序的生命周期
java·后端
AskHarries6 小时前
GitHub 登录配置流程
后端
Sinclair8 小时前
安企CMS的安装-常见安装问题排查
运维·后端
Sinclair9 小时前
安企CMS的安装-安装引导与初始化
运维·后端
Sinclair9 小时前
安企CMS的安装-源码编译安装
运维·后端·go
Sinclair9 小时前
安企CMS的安装-同一服务器多站点部署
运维·后端
Sinclair9 小时前
安企CMS的安装-服务器手动部署
运维·后端