golang开发GUI桌面应用(六)- wails,WebView2,postMessage,实现原理

简介

官网:https://wails.io/zh-Hans/

文档:https://wails.io/zh-Hans/docs/introduction/

github:https://github.com/wailsapp/wails

将它看作为 Go 的快并且轻量的 Electron 替代品。 您可以使用 Go 的灵活性和强大功能,结合丰富的现代前端,轻松的构建应用程序。

之前使用Electron开发过一个简单的桌面应用,打包后居然有一百多M,它不仅嵌入 Chromium 和 Node.js 到 二进制文件,而且包含完整的前端代码,对比,但是 Electron有一个超级应用,那就是 vscode。

功能

  • 原生菜单、对话框、主题和半透明
  • Windows、macOS 和 linux 支持
  • 内置 Svelte、React 、Preact 、Vue、Lit 和 Vanilla JS 的模板
  • 从 JavaScript 轻松调用 Go 方法
  • 自动将 Go 结构体转换为 TypeScript 模块
  • Windows 上不需要 CGO 或外部 DLL
  • 使用 Vite的实时开发模式
  • 可以轻松创建、构建和打包应用的强大命令行工具
  • 丰富的 运行时库
  • 使用 Wails 构建的应用程序兼容 Apple & Microsoft 商店

检查环境

要求:go 1.18+,node 15+

bash 复制代码
node --version
v20.11.0

npm --version
10.2.5

go version
go version go1.20 windows/amd64

安装 wails-cli

bash 复制代码
go install github.com/wailsapp/wails/v2/cmd/wails@latest

运行 wails doctor 将检查您是否安装了正确的依赖项。 如果没有,它会就缺少的内容提供建议以帮助纠正问题。输出如下

bash 复制代码
                                
          Wails Doctor          
                                


# Wails
Version | v2.9.2

# System
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| OS           | Windows 10 Education                                                                             |
| Version      | 1803 (Build: 17134)                                                                              |
| ID           |                                                                                                  |
| Go Version   | go1.20                                                                                           |
| Platform     | windows                                                                                          |
| Architecture | amd64                                                                                            |
| CPU          | Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz                                                          |
| GPU 1        | OrayIddDriver Device (Shanghai Best Oray Information Technology Co., Ltd.) - Driver: 17.1.58.818 |
| GPU 2        | Intel(R) HD Graphics 4600 (Intel Corporation) - Driver: 20.19.15.4835                            |
| Memory       | 16GB                                                                                             |
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

# Dependencies
┌───────────────────────────────────────────────────────┐
| Dependency | Package Name | Status    | Version       |
| WebView2   | N/A          | Installed | 130.0.2849.80 |
| Nodejs     | N/A          | Installed | 20.11.0       |
| npm        | N/A          | Installed | 10.2.5        |
| *upx       | N/A          | Installed | upx 4.1.0     |
| *nsis      | N/A          | Installed | v3.09         |
└─────────────── * - Optional Dependency ───────────────┘

# Diagnosis
 SUCCESS  Your system is ready for Wails development!

 ♥   If Wails is useful to you or your company, please consider sponsoring the project:
https://github.com/sponsors/leaanthony

创建项目

bash 复制代码
wails init -n wailsapp-1 -t vue

以dev模式运行

bash 复制代码
cd wailsapp-1
wails dev

会启动一个窗口展示页面,你还可以在浏览器上访问 http://localhost:34115/ 来打开你的页面,以方便在页面上调试。还有一个是 http://localhost:5173/,是 Vite Server,暂时不用管。

从任务管理器看到,Webview2 使用的是 Microsoft Edge ,但是文档里说的是 Chromium 。

Ctrl+Shift+F12,或者右键-->检查,可以打开DevTools,其目标是 http://wails.localhost:34115。当然,在 build 的时候可以设置禁用 DevTools。这个跟你在浏览器按F12打开的基本一样,功能差不多。

在启动的过程中命令行报了一个错误Generating bindings: 2024/11/17 15:20:34 Error: open D:\dev\php\magook\trunk\server\wailsapp-1\frontend\wailsjs\runtime\package.json: Access is denied.后来我使用管理员来运行 wails 程序还是有这个问题,暂时搁置。

端口5173是 npm 的 server,虽然也能打开网站,但是无法调用go的方法,因为没有执行bindings操作,因此缺少必要的js文件。

文档中也提到:当 Wails 为您的 index.html 提供服务时,默认情况下,它会将 2 个脚本注入 <body> 标签以加载 /wails/ipc.js/wails/runtime.js。 这些文件分别安装绑定和运行时。然后这两个js文件是不在你的项目里的,或者说还有很多文件是wails生成的然后被注入到 embed.FS 中,这个在下面讲原理的时候再说。

修改前端代码会立即在窗口中看到,修改了go代码也会自动重启程序。

在启动的时候会将go程序暴雷的方法生成js对应的方法,所以你只需要调用即可。

src就是一个纯粹的前端项目,比如你可以使用element-plus框架,前端通过调用wailsjs与go进行通信。

编译打包

wails build,打包后保存在 build 目录下。编译后只有8.69M,压缩后会更小,还是比较轻量级的,但是它的运行需要 WebView2 ,如果你电脑上没有,APP在运行的时候会先下载,但是谁的电脑上还不装个浏览器啥的,目前的windows系统都会自带 Edge,所以问题不大。

其思路大致为:将前端页面打包为embed.FS,以二进制的形式包含在go代码中,这个在golang项目中是常规操作。然后就是使用go调用webkit窗口,可以设置菜单,事件等功能。

它继承了go的跨平台编译特性,所以wails也是可以打包指定平台的应用,当然也有一些细节需要注意,文档中有说明。

它支持压缩二进制文件,使你的软件更小,比如upx,当然go本身也支持压缩编译

在编译的时候还可以设置 Webview2 参数,用来处理客户机器上的依赖问题,参考

所以,wails 这种实现比操作系统层面的UI组件要好用很多,虽然它们也会做跨平台的底层兼容(比如Fyne),但是使用HTML来堆UI交互显然容易很多;其次它比Electron要轻很多。

架构实现

我比较感兴趣的是它的JS Bingdings for Go Methods,即 js 是如何调用go方法以及如何拿到返回值。

可以先了解一下JSBridge的实现原理

我将使用 DevTools 来进行说明。

打开源代码,就能看到很多不在你项目中的文件

从 bindings.js 可以看到wails以map的形式,将go方法在js中做了绑定

js 复制代码
window.go[packageName][structName][methodName] = function(){...}

所以调用方式为

js 复制代码
export function Greet(arg1) {
  return window['go']['main']['App']['Greet'](arg1);
}

从 ipc.js 我们可以看到它在检查环境并定义window.WailsInvoke

js 复制代码
( () => {
    (function() {
        let n = function(e) {
            for (var s = window[e.shift()]; s && e.length; )
                s = s[e.shift()];
            return s
        }
          , o = n(["chrome", "webview", "postMessage"])
          , t = n(["webkit", "messageHandlers", "external", "postMessage"]);
        if (!o && !t) {
            console.error("Unsupported Platform");
            return
        }
        o && (window.WailsInvoke = e => window.chrome.webview.postMessage(e)),
        t && (window.WailsInvoke = e => window.webkit.messageHandlers.external.postMessage(e))
    }
    )();
}
)();

shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。

call.js中的call()方法可以看到发送的消息结构

js 复制代码
const payload = {
    name,
    args,
    callbackID,
};

// Make the call
window.WailsInvoke('C' + JSON.stringify(payload));

关于window.postMessage(),它的作用是向浏览器中的指定窗口发送消息,定义如下

bash 复制代码
window.postMessage(message, targetOrigin, [transfer])
  • message:发送到其他window的信息,字符串。
  • targetOrigin:目标窗口的url,空或者*表示向所有窗口广播消息。
  • transfer(可选参数):是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

接收方需要通过监听事件的方式来获得消息

js 复制代码
window.addEventListener("message",(e)=>{}, false)

消息 e 包含三个属性:

  • data:从其他 window 中传递过来的数据字符串。
  • orgin:发送数据的源地址url。
  • source:发送数据的window对象,您可以使用此来在具有不同 origin 的两个窗口之间建立双向通信。

调试calls.js文件的 Callback 函数,以查看go返回的数据。打断点,然后在窗口上输入8888,点击Great按钮。

incomingMessage是一个json字符串

json 复制代码
{"result":"Hello 8888, It's show time ok.","error":null,"callbackid":"main.App.Greet-3143955801"}

callbackid是自动设置的,我们绑定的go方法可以有两个返回值(result interface{}, err error),当然err是可选的。

从 Callback 函数的最后几行代码我们就知道在js代码中如何处理返回值,修改 HelloWorld.vue

js 复制代码
function greet() {
  Greet(data.name).then(result => {
    data.resultText = result
  }).catch(err => {
    data.resultText = err
  })
}

修改go中的Great方法

go 复制代码
// Greet returns a greeting for the given name
func (a *App) Greet(name string) (string, error) {
	return fmt.Sprintf("Hello %s, It's show time ok.", name), errors.New("test error")
}

等待它重新启动。

看到返回值为{"result":null,"error":"test error","callbackid":"main.App.Greet-3845362984"}

至此,整个流程就很清晰了。

具体的细节就不深究了,大致思路就是:利用 WebView2 为平台,js向窗口发送数据,go程序调用WebView2来监听消息事件,这样就拿到了数据,go向js响应数据也采用同样的方式,它们使用 callbackID 来一一对应。

之前在使用QQ音乐网页版的时候,注意到一个场景,你将查到的歌曲播放,它会新开一个播放页,也就是现在有两个页面,一个主页一个播放页,你在主页切歌了,播放页立马响应。

相关推荐
zjw_rp几秒前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob13 分钟前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
engchina29 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it30 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成1 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
Smile灬凉城6661 小时前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx2024061 小时前
SQL MID()
开发语言
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript