
简介
Wails 项目简化了使用 Go 编写跨平台桌面应用程序的过程。它的前端使用原生 WebView 组件(而非嵌入式浏览器),既能将全球最流行的 UI 系统的强大能力带到 Go 中,又能保持轻量。
2版本于2022年9月22日发布,带来了许多增强功能,包括:
- 利用热门的 Vite 项目进行实时开发
- 用于管理窗口和创建菜单的丰富功能
- Microsoft 的 WebView2 组件
- 生成与 Go 结构体对应的 TypeScript 模型
- 创建 NSIS 安装程序
- 混淆构建
目前,Wails v2 提供了强大的工具,可用于创建功能丰富的跨平台桌面应用程序。
本篇博文旨在审视项目目前的状况,以及我们今后可以改进的方面。
目前进展如何?
自 v2 发布以来,Wails 日益普及,这令人惊叹。社区的创造力以及大家使用它构建出的优秀成果总是令我赞叹不已。随着项目越来越受欢迎,关注它的人也越来越多,功能请求和错误报告也随之增加。
随着时间推移,我逐渐找出了项目面临的一些最紧迫的问题,也发现了一些制约项目发展的因素。
当前问题
我认为以下方面正在制约项目发展:
- API
- 绑定生成
- 构建系统
API
目前,用于构建 Wails 应用程序的 API 由 2 个部分组成:
- 应用程序 API
- 运行时 API
众所周知,应用程序 API 只有 1 个函数:Run()。该函数接收大量选项,用于控制应用程序的运行方式。这种 API 虽然非常易用,但也有很大局限性。它采用“声明式”方式,隐藏了许多底层复杂性。例如,它没有提供主窗口的句柄,因此你无法直接与主窗口交互。为此,你必须使用运行时 API。当你开始尝试创建多个窗口等更复杂的操作时,这就成了问题。
运行时 API 为开发者提供了许多实用函数,包括:
- 窗口管理
- 对话框
- 菜单
- 事件
- 日志
运行时 API 有很多地方令我不满意。首先,它要求在各处传递一个“上下文”。新开发者传入上下文后却遇到运行时错误,既令人沮丧又让人困惑。
运行时 API 最大的问题是,它是为仅使用单个窗口的应用程序设计的。随着时间推移,对多窗口的需求不断增长,而该 API 并不适合这种场景。
关于 v3 API 的设想
如果我们能像下面这样做,岂不是很好?
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}这种编程式方法直观得多,开发者可以直接与应用程序元素交互。目前所有用于窗口的运行时方法都可以直接成为窗口对象的方法。至于其他运行时方法,我们可以像下面这样将它们移到应用程序对象上:
app := wails.NewApplication(options.App{})
app.NewInfoDialog(options.InfoDialog{})
app.Log.Info("Hello World")这是一个强大得多的 API,可以用来构建更复杂的应用程序。它还支持创建多个窗口,而这正是GitHub 上获赞最多的功能:
func main() {
app := wails.NewApplication(options.App{})
myWindow := app.NewWindow(options.Window{})
myWindow.SetTitle("My Window")
myWindow.On(events.Window.Close, func() {
app.Quit()
})
myWindow2 := app.NewWindow(options.Window{})
myWindow2.SetTitle("My Window 2")
myWindow2.On(events.Window.Close, func() {
app.Quit()
})
app.Run()
}绑定生成
Wails 的一项关键功能是为 Go 方法生成绑定,以便从 JavaScript 调用这些方法。目前的实现方式有些取巧:先使用一个特殊标志构建应用程序,再运行生成的二进制文件,由它使用反射来确定绑定了哪些内容。这造成了一种先有鸡还是先有蛋的局面:没有绑定就无法构建应用程序,而不构建应用程序又无法生成绑定。有许多方法可以绕过这个问题,但最好的办法是完全不采用这种方式。
此前曾多次尝试为 Wails 项目编写静态分析器,但都没有取得太大进展。近来,随着相关资料日益丰富,实现这项工作的难度略有降低。
与反射相比,AST 方法快得多,但也复杂得多。一开始,我们可能需要对在代码中指定绑定的方式施加一定限制。目标是先支持最常见的用例,之后再逐步扩展。
构建系统
与 API 的声明式方式一样,构建系统也是为了隐藏构建桌面应用程序的复杂性而创建的。运行wails build时,它会在幕后执行许多操作:
-
为绑定构建后端二进制文件并生成绑定
-
安装前端依赖项
-
构建前端资源
-
确定应用程序图标是否存在,如果存在,则将其嵌入
-
构建最终二进制文件
-
如果构建目标是
darwin/universal,则会构建 2 个二进制文件,一个用于darwin/amd64,另一个用于darwin/arm64,然后使用lipo创建通用二进制文件 -
如果需要压缩,则使用 UPX 压缩二进制文件
-
确定是否要打包该二进制文件;如果要打包,则:
- 确保将图标和应用程序清单编译到二进制文件中(Windows)
- 构建应用程序包,生成并复制图标包,然后将二进制文件和 Info.plist 复制到应用程序包中(Mac)
-
如果需要 NSIS 安装程序,则构建该安装程序
整个过程虽然非常强大,但也极不透明。它很难自定义,也很难调试。
为了解决 v3 中的这个问题,我希望改用 Wails 之外的构建系统。在使用了一段时间的Task之后,我非常喜欢它。它是一款配置构建系统的出色工具,任何使用过 Makefile 的人应该都能比较容易上手。
构建系统将通过一个Taskfile.yml文件进行配置;使用任何受支持的模板时,默认都会生成此文件。该文件将包含执行当前所有任务(例如构建或打包应用程序)所需的全部步骤,便于自定义。
无需在外部安装此工具,因为它会成为 Wails CLI 的一部分。这意味着你仍然可以使用wails build,它会继续完成目前的所有工作。不过,如果要自定义构建过程,可以编辑Taskfile.yml文件。这也意味着你可以轻松了解各个构建步骤,并可按需使用自己的构建系统。
构建流程中尚缺的一环,是图标生成、压缩和打包等原子操作。如果要求开发者安装一大堆外部工具,体验并不好。为此,Wails CLI 将自身提供所有这些功能。这意味着构建仍可按预期工作,不需要额外的外部工具;但你也可以使用任何喜欢的工具替换任意构建步骤。
这将成为一个透明得多的构建系统,不仅更易于自定义,还能解决围绕它提出的许多问题。
带来的收益
这些积极变化将为项目带来巨大收益:
- 新 API 会更加直观,并支持构建更复杂的应用程序。
- 使用静态分析生成绑定会快得多,并能大幅降低当前流程的复杂性。
- 采用成熟的外部构建系统,将使构建过程完全透明,并支持强大的自定义能力。
项目维护者将获得以下收益:
- 新 API 将更易于维护,也更容易适配新功能和新平台。
- 新的构建系统将更易于维护和扩展。我希望这能催生一个由社区推动的全新构建流水线生态系统。
- 项目内部将实现更好的关注点分离,从而更易于添加新功能和新平台。
计划
相关的大量实验工作已经完成,目前进展良好。这项工作目前还没有明确的时间表,但我希望在2023第一季度末之前发布 Mac 版 Alpha,让社区能够测试、试用并提供反馈。
总结
- v2 API 采用声明式设计,向开发者隐藏了许多细节,并不适合多窗口等功能。我们将创建一个更简洁、更直观、功能更强大的新 API。
- 现有构建系统不透明且难以自定义,因此我们将改用外部构建系统,使整个构建过程完全开放透明。
- 绑定生成过程缓慢且复杂,因此我们将改用静态分析,以消除当前方法中的大量复杂性。
我们已为 v2 的内部核心投入了大量工作,现在它已经相当稳固。接下来该改进其上层,让开发者获得好得多的体验。
希望你和我一样对此感到兴奋。我期待听到你的想法和反馈。
此致,
‐ Lea
附言:如果你或你的公司认为 Wails 很有用,请考虑赞助本项目。谢谢!
再附言:没错,那确实是用 Wails 构建的多窗口应用程序的真实截图,不是效果图。这是真的,非常棒,而且即将推出。