electron 和 tauri 都不會用,那就自己寫個吧

前言

在《屠龍少年終成惡龍,前端轉產品的我給前端挖了個坑》這篇文章裏,有講到我是如何把我們的前端帶坑裏去。同時評論區有一條評論 你那個demo.exe是用什麼實現的?可以直接套殼現有的系統? 看起來好像是在坑外徘徊不定若有所思的樣子。因此我打算寫本文把他踹坑裏去,能踹一個是一個。

不想用 electron 和 tauri ?那我們一起來寫個像 electron 的垃圾玩意吧~ 我們的目標是:前端程序員無需會三方語言就可獨立完成桌面程序,創建托盤程序和服務、讀寫文件、處理進程、剪貼板這些都沒有問題,預計體積1M內,最大不超過2M。

爲什麼做

我當前已經使用 nodejs 開發一個命令行程序,這個程序的工具方式是,從網絡上獲取動態的配置,然後讀取這個配置進行啓動。啓動後就能去做其他額外的事情了,而不需要管這個程序。因爲這個程序只是一個輔助工具。

但是目前有一些痛點:

每次啓動的時候我都得先找到項目目錄,然後運行 node xxx.js,然後啓動一個黑框框,然後我再最小化這個框。啓動步驟相當麻煩並且有一個不用管的窗口在任務欄,相當礙眼。

有很多方法可以處理啓動問題,比如 pm2/快捷鏈接/全局安裝/製作 PKG 安裝包等。但各自有各自的問題,這裏不一一列舉。

對於有一個黑框需要最小化到任務欄問題,我嘗試過使用 node child_process 的 detached=false, windowsHide=true 等參數配合 pm2 都是沒有用的,黑框還是會彈出。 假設有用,我要的也不僅如此。

我覺得這個工具不錯,我想要把這個工具發給別人使用,雖然這個工具是 nodejs 寫的,但我不希望別人還要去學習安裝 nodejs 環境。雖然這個工具是命令行啓動,並支持參數配置,但我希望像常規程序一樣,別人點擊一個圖標就能啓動,可以從界面上配置參數。可以在界面上看到程序的實時日誌,最小化之後,變成一個小圖標在任務欄,不佔空間不礙眼。

那麼問題來了,因爲我經常用 html/css/js 畫界面,對很多前端組件庫比較熟悉,所以我打算用前端寫界面。但 js 是跑在瀏覽器裏的,讀取不了保存在電腦裏的配置文件,更實現不了托盤圖標功能,也運行不了 node 程序。

據我所知,像這種想使用前端語言開發界面,又需要與操作系統進行交互的功能,有不少方案。下面是我對他們的調研結果:

名稱 前端 後端 體積 MB 內存 MB 放棄原因 備註
nodegui chromium nodejs 100 100 體積大
miniblink49 Chromium nodejs ? ? 體積大 僅支持 window
NW.js Chromium nodejs 100 100 體積大
electron Chromium nodejs 100 100 體積大
Wails webview go 8M ? 需其他語言
Tauri webview rust 1 ? 需其他語言
Qt 可選 C++ 30 ? 需其他語言
wpf 可選 C# ? ? 需其他語言 僅支持 window
Muon Chromium go 42 26 需其他語言
Sciter Sciter QuickJS 5 ? 與普通瀏覽器和 nodejs 可能有差異
gluon 瀏覽器 nodejs 1 80 生態小,例如沒有找到托盤圖標實現方式
neutralino 瀏覽器 API 2M 60 api 不多

當前大家比較火有 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 頁面對系統進行操作的常規訴求。
  • 工具層:例如健壯性、安全性、自動升級、調試、打包、啓動等。

做成了什麼樣

下面這個圖片演示了啓動程序時,有一個綠色的進度條(不會遮擋鼠標),然後進入界面。

image.png

目前已過可行性驗證階段,給客戶做了一個文件管理系統程序,類似一個網盤,頁面由前端完成,然後文件的下載、預覽、同步這些交給 main 提供的 api。

下面這個圖片演示了在 web 中關閉程序。

image.png

對於自己的話,做了一個 ai 助手,對接的開源 ai-ui,已發給同事使用,也沒有問題。做了一個文章開關提到的助手程序,自己使用。

再次演示一下透明窗口,上面的啓動時的進度條也是使用透明窗口完成的。

image.png

演示自定義窗口標題和托盤。

image.png

程序啓動時的進度條也是使用 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 社區中發現有 https://www.npmjs.com/package/rpc-websockets 這個包可用,還兼容 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

計劃三:儘快完成 api 的封裝和文檔,讓前端朋友只調用指定的 js api 即可完成托盤、進程、剪貼板、IO等系統操作。我們封裝的 api 儘量向 neutralino 靠近,做到最小成本的遷移。等它成熟在,可以遷入,沒成熟之前我們也能自己用着。

需要什麼幫助

可以幫我們封裝 api,這需要你瞭解 main 的語言;可以用 main 來做些小工具嘗試一下,這就是最好的幫助;可以做操作系統風格皮膚,等你做好了,electron 和 tauri 他們都能用,因爲他只是 css;或者可以點個 star https://github.com/wll8/sys-shim

好了,餅畫了,牛吹了,坑挖了,我要去玩了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章