Electron入門指北

沉迷程式碼的萌新發表於2020-11-13

最近幾年最火的桌面化技術,無疑是Qt+Electron
兩者都有跨平臺桌面化技術,並不侷限於Windows系統。前者因嵌入式而誕生,在演變過程中,逐步完善了生態以及工具鏈。後者則是依託於Node.JsCCM(Chromium Content Module),支援Node.jsNode.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原生的編寫和實現。

相關文章