最近幾年最火的桌面化技術,無疑是Qt+和Electron。
兩者都有跨平臺桌面化技術,並不侷限於Windows系統。前者因嵌入式而誕生,在演變過程中,逐步完善了生態以及工具鏈。後者則是依託於Node.Js和CCM(Chromium Content Module),支援Node.js和Node.js原生以及自主封裝的Electron API。
因為專案考慮跨平臺的技術選型,研究過長達一年的Electron,並且做了很多嘗試,所以想與諸君分享Electron的技術心得。
Electron的特點
Electron依賴於Node.js,只要會用Node.js開發程式的,都可以用Electron開發桌面應用,只需要前端結合Electron API,就可以快速完成桌面應用,一處程式碼,多處編譯。
Electron的優勢
可以編譯在Windows、Mac、Linux的X64/x86環境,因為是系統獨立打包,不依賴執行時。
頁面開發依賴於Node.js加前端,可以很便捷的採用前端開發頁面,再通過Electron API結合的形式,Electron API可以理解為一套Node原生庫,實際上安裝也就是一句 npm install electron
Electron核心採用Chromium,可以相容主流瀏覽器,並不需要額外適配,而且採用Chromium LST(長期支援版本),並不會過於激進的選擇最新版本,減少了前端為了相容適配帶來的風險性。
另一方面,採用前端做UI層,可以減少UI對於系統的適配情況。
Electron的應用場景
由於採用了Node.js和前端的開發模式,本身是一個巢狀了瀏覽器的本地化前端應用,適合一些沒有底層操作的應用場景,甚至前端頁面可以是遠端地址,這樣子完全可以完成桌面應用的無縫熱更新
技術的侷限性
Electron採用的核心架構是Node.js,所以一旦涉及非頁面展示層的功能,比如要與com互動,要操作底層庫,比如各種硬體互動的SDK XX.dll,這裡就需要參考Node原生來編寫原生擴充套件了。
Node原生編寫
參照Node.js官網的原生擴充套件,編寫一個簡單的例子
執行npm init
建立一個預設的package.json
以下程式碼儲存為 hello.cc
#include <node.h>
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void Method(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(
isolate, "world"));
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
以下程式碼儲存為 binding.gyp
{
"targets": [
{
"target_name": "hello",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"sources": [ "hello.cc" ],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
}
]
}
執行兩個命令 node-gyp configure node-gyp build
如果提示命令不存在,則 npm install node-gyp
命令很簡單 前者是生成專案檔案,後者則是編譯檔案,生成 hello.node ,至於為什麼叫hello.node,可以參考 binding.gyp/targets/target_name
以下程式碼儲存為 index.js
var addon = require('./build/Release/hello');
console.log(addon.hello()); // 'world'
執行 node . ,顯示
遭遇滑鐵盧
好像一切都挺順利的
按照Electon的例子,編寫index.js
const { app, BrowserWindow } = require('electron')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
win.loadURL('https://electronjs.org')
var addon = require('./hello');
console.log(addon.hello()); // 'world'
}
console.log(process.versions.node);
app.whenReady().then(createWindow);
程式碼本身可以執行起來,但是加入了呼叫Node原生之後就提示
這明顯不能用啊,而且很認真的保持了Electron內部Node.js和外部編譯外掛的Node.js的一致性
柳暗花明
參考了electron官方例子未果後,反覆搜尋資料,一一驗證,最後發現,要在node-gyp編譯的時候,標註版本
node-gyp configure --target=版本號 --dist-url=https://atom.io/download/atom-shell
node-gyp build --target=版本
看見了node-gyp,你以為是編譯的node對應版本的外掛?no,這裡編譯的是electron對應的版本
程式執行成功的那一刻,我差點口吐芬芳,不帶這麼坑人的
社會的險惡
程式也跑起來了,是不是 “天晴了雨停了,感覺自己又行了”
這才第一步呢,實際上node.js原生的寫法是專屬寫法,帶有大量的侵入式的外掛設計,這塊不亞於重寫學一門C語言的亞種 (比如Object-C和C的差別)
這還只是自己撰寫原生擴充套件,如果是海量API組合的呢?這個擴充套件要寫成什麼樣?所幸社群開發了一種神器,叫FFI
此時應有掌聲
你以為這裡是為你鼓掌的?不,這只是開啟了探索星辰大海的第一步,告訴你,不要哭,未來哭的日子還長呢。
FFI的編譯文章千千萬,編譯不成功的文章更是數不勝數,沒有一篇資料闡述了FFI與Node之間晦澀不明的版本關係 ,也沒有一篇文章告訴你,如何成功安裝。
成功的都是僥倖,失敗,那才是人生。
小夥計,你已經上了黑車,車門焊死了。
Electron常見的雷區
Electron的瀏覽器核心叫 CCM(Chromium Content Modult),是不是有CEF小夥伴已經嗷嗷叫了?這個自帶了mp4支援,不用再編譯了,是不是更興奮了,幸福來的太突然?
先別忙,瞅一眼Electron API,你品,你細品。
是不是沒有CEF常見的各種擴充套件Handler?沒錯,CEF重度產品,至少在預設的Electron框架上,是很多做不了的,比如擷取瀏覽器區域的webgl renderer/render的產品,比如各種音樂播放器,基於音訊的Handler二次混合疊加的產品,在預設實現上,做不了。
對於前端框架的改動
常見的一些前端小元件被移除,常見的比如localstroge、sessionstroge不可用,需要搜尋一些三方的元件替代性使用,比如electron-localstroge。
常見的Window.Open被修改,實現指向了Electron.BrowserWindow。
釋出
Electron本身的釋出其實沒有什麼問題的,一旦引入了原生擴充套件,釋出的時候,就需要小心的重新編譯Electron再發布,不然會出現某知名線上教育機構的Electron桌面應用,釋出到客戶電腦上,報錯。
咳咳咳,具體是哪家,就不提了,我們也不知道,我們也不敢說。
後記
寫到這裡,只是想給對Electron感興趣的小夥伴,一個冷靜的分析,自己所在的團隊,是否能撐得起這套框架的成本,看得見的便利性是存在的,看不見的隱藏成本,是否可以為此買單。
肯定會有很多微軟狂熱粉 VS Code也用的Electron啊!
實際上對於那種技術研發的大型公司,用什麼技術,已經不再是難題了,選擇了Electron,也只是為了擁抱社群做的妥協,並不代表這個技術的選型是最佳方案。
技術的選擇依舊是 自己的團隊是否可以承擔簡單功能外的Node原生的編寫和實現。