electron 和 tauri 都不想用,那就自己写个想用的吧

14,699 阅读10分钟

前言

在《屠龙少年终成恶龙,前端转产品的我给前端挖了个坑》这篇文章里,有讲到我是如何把我们的前端带坑里去。同时评论区有一条评论 你那个demo.exe是用什么实现的?可以直接套壳现有的系统? 看起来好像是在坑外徘徊不定若有所思的样子。因此我打算写本文把他踹坑里去,能踹一个是一个。

不想用 electron 和 tauri ?那我们一起来写个像 electron 的垃圾玩意吧~ 我们的目标是:前端程序员无需会三方语言就可独立完成桌面程序,创建托盘程序和服务、读写文件、处理进程、剪贴板这些都没有问题,预计整体体积不超过1M。

为什么做

我当前已经使用 nodejs 开发一个命令行程序,这个程序的工具方式是,从网络上获取动态的配置,然后读取这个配置进行启动。启动后就能去做其他额外的事情了,而不需要管这个程序。因为这个程序只是一个辅助工具。

但是目前有一些痛点:

每次启动的时候我都得先找到项目目录,然后运行 node xxx.js,然后启动一个黑框框,然后我再最小化这个框。启动步骤相当麻烦并且有一个不用管的窗口在任务栏,相当碍眼。

有很多方法可以处理启动问题,比如 pm2/快捷链接/全局安装/制作 PKG 安装包等。但各自有各自的问题,这里不一一列举。

对于有一个黑框需要最小化到任务栏问题,我尝试过使用 node child_process 的 detached=false, windowsHide=true 等参数配合 pm2 都是没有用的,黑框还是会弹出。 假设有用,我要的也不仅如此。

我觉得这个工具不错,我想要把这个工具发给别人使用,虽然这个工具是 nodejs 写的,但我不希望别人还要去学习安装 nodejs 环境。虽然这个工具是命令行启动,并支持参数配置,但我希望像常规程序一样,别人点击一个图标就能启动,可以从界面上配置参数。可以在界面上看到程序的实时日志,最小化之后,变成一个小图标在任务栏,不占空间不碍眼。

那么问题来了,因为我经常用 html/css/js 画界面,对很多前端组件库比较熟悉,所以我打算用前端写界面。但 js 是跑在浏览器里的,读取不了保存在电脑里的配置文件,更实现不了托盘图标功能,也运行不了 node 程序。

据我所知,像这种想使用前端语言开发界面,又需要与操作系统进行交互的功能,有不少方案。下面是我对他们的调研结果:

名称前端后端体积 MB内存 MB放弃原因备注
nodeguichromiumnodejs100100体积大
miniblink49Chromiumnodejs??体积大仅支持 window
NW.jsChromiumnodejs100100体积大
electronChromiumnodejs100100体积大
Wailswebviewgo8M?需其他语言
Tauriwebviewrust1?需其他语言
Qt可选C++30?需其他语言
wpf可选C#??需其他语言仅支持 window
MuonChromiumgo4226需其他语言
SciterSciterQuickJS5?与普通浏览器和 nodejs 可能有差异
gluon浏览器nodejs180生态小,例如没有找到托盘图标实现方式
neutralino浏览器API2M60api 不多

当前大家比较火有 electron 和 tauri。四年前我使用过 electron 做过一个桌面划词程序,由于涉及到系统操作,所以需要安装 node-gyp/pytohn/visual studio 等依赖来进行本地编译,能否操作成功与 electron/node/node-ffi 等版本兼容性有很大的关系,安装过程和 electron 的体积都给我留下了不好的印象,另外 electron 里的主进程、渲染进程、通信的一些使用上的差异,也让我觉得不那么便利,所以我放弃 electron 。

接下来就是 tauri,它由于不打包 nodejs 和 chromium ,所以体积较小。但我看他官网上的 demo,就连启动都 rust 代码。

2023-11-23-19-10-03.png

虽然代码没几行,但我也是相当拒绝:说好的只使用前端语言就能写桌面程序呢?

所以我放弃了 tauri 。原因是我真想找一个不使用三方语言就能做桌面程序的工具。我发现 neutralino 比较贴近我的需求,但它当前还很年轻,很多 api 和示例都没有。这相当于如果遇到了操作系统层面上的问题,只要他不提供 api 我就没法操作,因为我不会写原生代码,所以又放弃了 neutralino 。

所以就自己做一个吧。

准备怎么做

准备使用当前了解的一个语言做一个基于 webview 的工具,我们暂且叫 main。它加载好前端页面,并向前端页面注入 api 并连接上 websockets 。如果前端有什么对系统操作的诉求,告诉 main 即可,由 main 完成,对于前端而言,就像调用一个普通的 js 方法一样,传参、处理结果、完事。

语言名为 aardio ,由于“各种原因”这里不做过多叙述。后面文档中统一称其为语言。

那么为什么都去搞一个语言了不搞 rust 这些?有几点考虑:

  • 提供了 js/webview/nodejs 互相调用的例子
  • 提供了一些常见的系统托盘、窗口操作示例等
  • 我对作者维护这个语言这么多年心存敬畏

程序的整体架构是这样的:

  • 配置层:常用的定制化需求,都可以通过一个 json 配置文件解决。js处理起来也简单。
  • 依赖层:比如注入到 web 页的经过封装的 js 文件。
  • 内核层:完成与 web 页面的通信,满足 web 页面对系统进行操作的常规诉求。
  • 工具层:例如健壮性、安全性、自动升级、调试、打包、启动等。

做成了什么样

下面这个图片演示了启动程序时,有一个绿色的进度条,然后进入界面。

screenshots.gif

目前已过可行性验证阶段,给客户做了一个文件管理系统程序,类似一个网盘,页面由前端完成,然后文件的下载、预览、同步这些交给 main 提供的 api。

下面这个图片演示了在 web 中关闭程序。

screenshots.gif

对于自己的话,做了一个 ai 助手,对接的开源 ai-ui,已发给同事使用,也没有问题。做了一个文章开关提到的助手程序,自己使用。

再次演示一下透明窗口,上面的启动时的进度条也是使用透明窗口完成的。

screenshots.gif

演示自定义窗口标题和托盘。

screenshots.gif

程序启动时的进度条也是使用 html 实现的

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>loading...</title>
    <style>
      body,
      html {
        height: 100%;
        overflow: hidden;
      }
      body {
        display: flex;
        align-items: center;
        justify-content: center;
      }
      @property --progress {
        syntax: '<percentage>';
        inherits: false;
        initial-value: 0%;
      }

      .g-progress {
        margin: auto;
        width: 240px;
        height: 10px;
        border-radius: 25px;
        background: linear-gradient(90deg, #0f0, #0ff var(--progress), transparent 0);
        border: 1px solid #eee;
        transition: .3s --progress;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div id="progress" class="g-progress progress-bar-striped" style="--progress: 10%"></div>
    </div>
  </body>
</html>

遇到的问题及处理方案

官方示例中给到的 webview 交互示例通过 external 注入到页面的 window 上,通过此方法能让 js 中的数据和 main 进行类型转换(比如 js 里传一个 number,那么到 main 里也是 number),还提供了一些可以直接启用 main 里对象方法的操作。好用是好用,但是与 nodejs 交互的时候,没有这种自动转换的功能,而且示例中的 node 服务连接很慢。

为了让 main 支持 webview 和 nodejs,并且使用方法统一,并且加快启动速度,查了一些资料,发生像这种跨语言通信通常都是使用 rpc 协议完成的,有 json-rpc/http-rpc/rpc-ws 等,为了实时性更强,我选择了 websockets 这种方式, 我 npm 社区中发现有 www.npmjs.com/package/rpc… 这个包可用,还兼容 node 和浏览器,尝试过后选择了它,这解决了跨语言通信问题。

另一个问题是,mian 中有很多方法是现成的。比如以下代码在 main 中可以使用:

// 有一个 winform 对象

winform.hitMax() // 最大化
winform.show() // 显示窗口
winform.hwnd // 获取窗口句柄
winform.hitCaption() // 拖动窗口
winform.text = "title" // 设置窗口标题
// ... 上百个现在的方法和属性

如果我们要为 js 提供 api, 我们是每个属性和方法都得去写吗?这又麻烦,代码又还臃肿。

经过一波挣扎,我想起了使用代理这种方式去实现,还是 js。

const obj = new Proxy(
  {},
  {
    get: function (target, key, receiver) {
      console.log(`getting ${key}!`);
      return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
      console.log(`setting ${key}!`);
      return Reflect.set(target, key, value, receiver);
    },
  }
);

根据 proxy,我们可以实现拦截到某个对象的方法调用和属性访问、设置等。再加上深层代理的话,像 winform.process.close() 这种有任何层方法属性都没有问题。

同时,在 main 中我们有这样的代码,来处理 proxy 拦截到的每个 key path:

image.png

我们把拦截到的 path ,比如在 js 里写 winform.process.close(true) 的时候,我们把拦截到的 winform.process.close 和参数 true 通过 rpc-ws 提供的 call 方法传给 main,这时候 main 根据 path 去动态调用函数并把参数传进去。我们把执行结果又丢给 call 方法返回给我们的 js 即可。

那么问题又来了,既然都实现了在 js 里调用方法和访问属性都像在写 main 中的代码一样,那真的就能不能以 js 的形式去写 main 的代码呢?看了一天的教程,发现这水很深啊,约等于创造一门语言,怕了怕了,逃。

但是思路着要有吧?好的:

如果简单一些呢,我们依然可以使用 proxy,实现操作符的拦截,从而实现一些简单的加减乘除的操作。然这没什么用啊,我们要实现的是比如用 js 里对 winform 对象进行遍历之前,我们就要做一个生成器之类的东西,在生成器的每一步里,去获取 main 里的遍历结果。感觉上好像能实现,实际我也不知道我在说什么。但是就算实现了,像这种遍历器,频繁的语言交互应该会消耗大量时间,感觉应该得不偿失。

所以在 js 里获得 main 中语言的编写体验,就不实现啦。如果我们真的要在 js 里写另一种语言,我们开放一个类似 js 的 eval 的功能。它可以向 main 传原生代码和参数。

// 创建目录
const dir = `C:/my/`
await ws.call(`run`, [
`
  fsys.createDir(arg)
`, dir])

例如上面这段代码,直接传送目录参数 C:/my/ 到 arg,使用原生语言 fsys.createDir(arg) 去执行。

后期计划是什么

计划一:使用 main 去做更多的桌面 app,以此促进 main 的完善。

计划二:为某个当前成熟的 ui 框架制定一套 css 皮肤,例如 win7 皮肤 ,例如 element-ui 样子很 web,但应用了这个皮肤之后,整体页面风格和控件都看起来就像原生 win7 桌面程序一样。

image-1.png

注:上面的整个窗口也是用 html/css 写的,不是真的 win7 窗口。

计划三:尽快完成 api 的封装和文档,让前端朋友只调用指定的 js api 即可完成托盘、进程、剪贴板、IO等系统操作。我们封装的 api 尽量向 neutralino 靠近,做到最小成本的迁移。等它成熟后,可以迁入,没成熟之前我们也能自己用着。

需要什么帮助

可以帮我们封装 api,这需要你了解 main 的语言;可以用 main 来做些小工具尝试一下,这就是最好的帮助;可以做操作系统风格皮肤,等你做好了,electron 和 tauri 他们都能用,因为他只是 css;或者可以点个 star github.com/wll8/sys-sh…

好了,饼画了,牛吹了,坑挖了,我要去玩了。