Electron
原名Atom Shell.
自Electron 28開始, 支援ESM.
參考: https://github.com/Elanis/web-to-desktop-framework-comparison
NW.js(原名node-webkit)是Electron的主要競爭對手, 二者同樣是Chromium+Node.js架構的跨平台桌面應用程式解決方案.
NW.js比Electron創立早2年, 但Electron現已遠比NW.js受歡迎, 並且情況不太可能在未來改變.
NW.js比Electron創立早2年, 但Electron現已遠比NW.js受歡迎, 並且情況不太可能在未來改變.
- Electron分離了Main程序和Renderer程序.
Renderer程序內分為兩個上下文, 分別執行Node.js程式碼和瀏覽器端程式碼.
Electron的模型分離了關注點, 但這種強制分離關注點的設計是否有意義是非常值得商榷的.
異常繁瑣的IPC設計和源源不斷的安全作秀使開發體驗變得極差.
異常繁瑣的IPC設計和源源不斷的安全作秀使開發體驗變得極差.
- NW.js基於Chrome Apps架構, Node.js和Chromium渲染器共享同一程序, 可以直接在瀏覽器端使用Node.js模組.
由於可以在瀏覽器端直接載入Node.js原生插件, NW.js的性能遠遠優於依賴IPC的Electron和基於WASM的方案.
有分離上下文和混合上下文兩種模式:
- 分離上下文模式下, 分為Node上下文和Web上下文兩個環境, Node上下文是Node.js模組的運行環境, Web上下文是NW.js腳本的運行環境.
由於不同上下文的全局變量是隔離的, 容易出現引用相等性問題. - 混合上下文模式下, 只使用一個上下文.
不會出現引用相等性問題, 但存在被第三方庫污染上下文的風險.
- Electron的入口點是Main程序的JavaScript腳本.
- NW.js入口點主要是HTML檔案, 但也可以是JavaScript腳本.
Electron的文件顯著優於NW.js的文件, 後者的文件可讀性差且嚴重缺乏更新.
- Electron沒有原生的原始碼保護, 需要使用bytenode或electron-vite這樣的V8字節碼方案.
- NW.js有官方提供的程式碼編譯器nwjc, 原理跟bytenode相同, 但實現略有不同:
因為NW.js可以直接修改V8, 從而避免了bytenode這樣的外部方案所存在的許多局限性.
- Electron有很多商業公司用戶, 最著名的項目是VSCode.
- 儘管其起步較早, NW.js現存的用戶量比較少.js.
RPG Maker製作的遊戲可能是NW.js目前最大的用戶來源.
Neutralinojs不打包Chromium, 而是使用作業系統上的WebView.
WebView可想而知會帶來兼容性問題, 因此不會被考慮為生產級方案.
WebView可想而知會帶來兼容性問題, 因此不會被考慮為生產級方案.
Neutralionjs不支援較舊的Windows系統: https://github.com/neutralinojs/neutralinojs/issues/486
Tauri使用Rust和作業系統的WebView, 不支援Node.js模組, 需要用Rust編寫API.
WebView可想而知會帶來兼容性問題, 因此不會被考慮為生產級方案.
WebView可想而知會帶來兼容性問題, 因此不會被考慮為生產級方案.
除了桌面平台, Tauri還計劃支援iOS和Android.
- 上下文隔離製造了不必要的複雜性.
- API缺乏統一性, 光是移除視窗的菜單欄就有多種方法.
- 過快的主版本迭代策略.
- 缺乏有用的測試框架.
Electron應用程式的主程序, 是整個應用程式的入口, 一個實例裡只能有一個.
Electron應用程式裡負責用戶界面的程序, 由主程序啟動, 一個實例裡可以有多個.
渲染器程序在默認情況下不具備直接訪問本機Node.js模組的能力,
所有Node.js模組都需要通過由preload腳本創建的上下文隔離接口來間接調用.
所有Node.js模組都需要通過由preload腳本創建的上下文隔離接口來間接調用.
可以在創建視窗時通過
nodeIntegration: true 選項啟用渲染器程序的Node.js集成.默認情況下, preload腳本與渲染器程序的宿主環境是隔離的,
兩者只能通過
兩者只能通過
contextBridge 和 window.postMessage 進行通信.可以通過
contextIsolation: false 取消這一行為.contextBirdge.exposeInMainWorld 是在preload腳本裡向渲染器程序的宿主環境暴露API的唯一方法.橋接時可用的數據類型受到限制:
除了基本的可結構化克隆的類型以外, 還允許發送函數, Promise, 錯誤等對象, 但仍然不能滿足常見需求.
除了基本的可結構化克隆的類型以外, 還允許發送函數, Promise, 錯誤等對象, 但仍然不能滿足常見需求.
橋接還具有以下特性:
- 對preload來說, API暴露是一次性的, 不能重複暴露, API的變化也不能同步到宿主環境.
- 對宿主環境來說, 遠程函數內部會顯示成native code.
- 暴露的對象會被凍結, 因此對宿主環境來說, API不可寫.
- 對不支援的類型, 可能會出現傳
undefined/對象屬性拷貝/報錯這三種不同的結果.
renderer程序可以使用ipcRenderer向main程序發出請求.
這種ipc支援兩種模式:
- send: 僅向ipcMain發送消息
- invoke: request-reply模式
由於上下文隔離, renderer程序裡調用
ipcRenderer.send/invoke 是很不方便的.main程序可以向renderer程序發出請求, 但前提是先得到renderer程序的WebContents對象.
WebContents對象有以下兩種取得方式:
- 由renderer首先向main發出請求, main接收到的
event.sender就是WebContents對象. - main保留創建視窗時的BrowserWindow, 然後使用
BrowserWindow.webContents.
由於上下文隔離, 在renderer程序裡註冊事件監聽器是很不方便的.
標準MesssagePort接口.
https://www.electronjs.org/docs/latest/tutorial/message-ports
https://www.electronjs.org/docs/latest/tutorial/message-ports
適用於main和renderer, renderer和renderer之間的通信.
由於main和renderer之間可以通過ipcMain和ipcRenderer通信,
因此這種通信方式主要是用於renderer和renderer之間的通信.
由於main和renderer之間可以通過ipcMain和ipcRenderer通信,
因此這種通信方式主要是用於renderer和renderer之間的通信.
建立通信前, 首先要通過ipcMain和ipcRenderer傳遞MessagePort對象.
藉助網絡協議可以實現IPC, 例如HTTP和WebSocket.
考慮到在Electron裡建立Main和Renderer(Web上下文)的通信是如此繁瑣,
使用基於網絡協議的作為IPC降低複雜性也是一種方法.
使用基於網絡協議的作為IPC降低複雜性也是一種方法.
- IPC接口會暴露給同一網絡的其他程序
- 性能比其他方法差.
由於ABI不同, Electron不能直接使用Node.js的native模組, 模組必須專門為Electron編譯.
https://www.electronjs.org/docs/latest/tutorial/devtools-extension
Electron有限度地支援Chrome擴展程序API, 可以載入一些開發工具.
在Windows和macOS上, electron自帶的API可以用於設置開機啟動:
https://www.electronjs.org/docs/latest/api/app#appsetloginitemsettingssettings-macos-windows
https://www.electronjs.org/docs/latest/api/app#appsetloginitemsettingssettings-macos-windows
然而, 相關功能的實現充滿了可悲的bug:
- https://github.com/electron/electron/issues/32657
- https://github.com/electron/electron/issues/33308
該實現不支援自定義args.
專門用於將配置項持久化為JSON檔案的方案.
底層使用了同作者的conf模組, 支援JSON Schema和基於
底層使用了同作者的conf模組, 支援JSON Schema和基於
package.json 的版本遷移.一個在內存中運行的文檔資料庫, 間歇性持久化到檔案系統.
開發不積極.
純JavaScript模組, 用JSON作為資料庫.
這玩意說白了就是一個用於讀取和寫入JSON的類, 沒有任何亮眼之處.
作者停止維護了.
將RxJS和PouchDB結合起來的一個高級資料庫.
由於使用PouchDB的生態系統, 官方支援Electron.
由於使用PouchDB的生態系統, 官方支援Electron.
下載量與Github Stars數不成比例的典型代表, 實際的用戶量並不多.
儘管作者對這個項目付出很多努力, 但該項目對於新用戶來說仍然具有陡峭的學習曲線.
儘管作者對這個項目付出很多努力, 但該項目對於新用戶來說仍然具有陡峭的學習曲線.
這個項目還依賴了太多作者無力控制的軟體包, 導致該項目變得非常脆弱:
- RxJS
- PouchDB
- pouchdb-find(RxCollection的find方法直接使用了此包, 因此共享相同的缺陷)
- mquery(複製了程式碼)
從v10版本起, RxDB開始為脫離PouchDB而做準備.
無論從項目的流行程度, 可維護性, 還是其依賴項的可靠性考慮, RxDB都不值得使用.
在Node.js裡, PouchDB通過levelup模組利用LevelDB作為儲存底層,
levelup模組的底層是native模組, 但它原生支援Electron, 因此不需要用electron-rebuild處理就能使用.
levelup模組的底層是native模組, 但它原生支援Electron, 因此不需要用electron-rebuild處理就能使用.
PouchDB並非為多程序場景而設計, 有很多基於內存的優化, 並且其Node.js底層LevelDB也不支援多程序.
因此在Electron使用時, 應該只在Main程序裡創建它的實例, 然後由IPC暴露給Renderer訪問.
因此在Electron使用時, 應該只在Main程序裡創建它的實例, 然後由IPC暴露給Renderer訪問.
pouchdb-find是用於實現Mongo式查詢語法的官方擴展.
這個包存在很多問題, 包括但不限於:
- 在有索引的情況下反而無法得到正確結果:
https://github.com/pouchdb/pouchdb/issues/7810
儘管相關issue被關閉, 但我仍然在v7.2.2的包裡遇到了相同的問題. - 丟棄選擇器:
https://github.com/pouchdb/pouchdb/issues/6572
https://github.com/pouchdb/pouchdb/issues/6371
依賴該包進行查詢顯然是不可靠的.
在沒有Mongo支援的情況下, PouchDB沒有使用價值.
在沒有Mongo支援的情況下, PouchDB沒有使用價值.
上一個版本是2020年發布的, 期間的bug沒有被修復, 有很多關鍵bug的issue被維護者忽視.
其類型定義
其類型定義
@types/pouchdb 也是舊的(適配的是PouchDB v6.4).RxDB的作者在RxDB v10的發行註記中也認為PouchDB沒有得到良好的維護.
https://github.com/pouchdb/pouchdb/issues/3224
作為一個不支援Node-API(N-API)的native模組,
better-sqlite3需要用electron-rebuild處理後才能使用, 這可能帶來依賴鎖定問題.
better-sqlite3需要用electron-rebuild處理後才能使用, 這可能帶來依賴鎖定問題.
這是Ghost團隊維護的SQLite3模組.
該模組自v5版本開始基於Node-API重寫.
該模組自v5版本開始基於Node-API重寫.
callback接口還設置了this, 導致無法使用箭頭函數.
為了獲得Promise包裝, 需要安裝第三方包裝器node-sqlite.
該庫的每個Database實例只是一個連接, 不存在任何競爭條件.
當開始事務期間, 在實例上運行的所有的語句都會成為該事務的一部分.
因此, 總是需要創建一個新的實例來開始事務, 為了複用實例, 意味著需要創建池, 這使解決方案變得複雜.
當開始事務期間, 在實例上運行的所有的語句都會成為該事務的一部分.
因此, 總是需要創建一個新的實例來開始事務, 為了複用實例, 意味著需要創建池, 這使解決方案變得複雜.
這是VSCode使用的SQLite3庫, 它是sqlite3的fork.
https://www.electronjs.org/docs/latest/tutorial/application-distribution
本質上, 應用程式分發是通過分發滿足特定目錄結構的預構建Electron二進制包來實現的.
例如, 在Windows上, 人工分發的方法是下載對應版本, 對應平台的預構建好的Electron二進制包,
在Electron目錄下創建
之後, 通常還需要對Electron可執行檔案改名和更改圖標.
例如, 在Windows上, 人工分發的方法是下載對應版本, 對應平台的預構建好的Electron二進制包,
在Electron目錄下創建
resources/app 目錄, 然後將整個項目檔案拷貝到此目錄下.之後, 通常還需要對Electron可執行檔案改名和更改圖標.
官方的electron-packager包可用來簡化以上步驟.
Electron專用的一種類似tar的檔案格式, 旨在將Electron工程打包成單個檔案.
resources/app.asar 檔案可以替代 resources/app 資料夾.一些本機模組在打包成asar檔案後會彈出此錯誤.
在不使用asar打包的情況下, Electron可以正確找到並載入本機模組.
在不使用asar打包的情況下, Electron可以正確找到並載入本機模組.
使用
--asar.unpack=*.{node,dll} 替換electron-packager的 --asar 可以解決此問題.參考:
- https://github.com/electron/packager/issues/424
- https://github.com/electron/asar/issues/75
app.isPackaged 被廣泛用於判斷Electron應用程式是處於開發環境還是生產環境.然而, 它的判斷方式比較反直覺, 它是通過檢查Electron可執行檔案的檔案名是否仍為electron來實現的.
該包用來自動化創建各個平台的應用程式.
- 支援設置可執行檔案圖標.
- 支援刪除devDependencies以縮小打包體積.
electron-packager缺少按ignore檔案忽略打包路徑的特性, 雖然曾經有過
(拒絕的理由缺乏邏輯和事實支撐).
缺乏此特性導致electron-packager變成一個無用的包, 因為除非在命令行裡大量添加ignore參數, 否則會將開發環境一同打包.
.electronignore 檔案的PR, 但是被拒絕(拒絕的理由缺乏邏輯和事實支撐).
缺乏此特性導致electron-packager變成一個無用的包, 因為除非在命令行裡大量添加ignore參數, 否則會將開發環境一同打包.
該項目在啟用
檢查原始碼之後發現, 該程序只是將所有任務并行, 而這在electron v28意味著同時下載10個electron.
考慮到伺服器的頻寬和下載方的頻寬, 這種毫無節制的并行只是在增加遇到網絡錯誤的概率.
--all 選項時對I/O和CPU資源的使用明顯高於它所執行任務所需的程度.檢查原始碼之後發現, 該程序只是將所有任務并行, 而這在electron v28意味著同時下載10個electron.
考慮到伺服器的頻寬和下載方的頻寬, 這種毫無節制的并行只是在增加遇到網絡錯誤的概率.
Windows安裝包的開源解決方案.
由electron-builder支援.
由社區包electron-wix-msi支援.
Windows 8開始支援的UWP格式, 可發布到Microsoft Store.
electron-builder可使用MakeAppx工具將程序打包為appx.
微軟於2018年開始支援的主要格式, 意在替代MSI, 用於Windows 10以上系統, 可發布到Microsoft Store.
- Electron社區還不存在MSIX的打包方案.
- 打包解決方案提供商Advanced Installer支援將Electron項目打包為MSIX.
- 微軟官方提供的MSIX Packaging Tool可用來將EXE或MSI安裝包重新打包為MSIX:
https://docs.microsoft.com/en-us/windows/msix/packaging-tool/create-app-package
Electron有一個官方維護的基於ChromeDriver的測試庫Spectron, 但最終由於缺乏維護者於2022年初被棄用.
Spectron的潛在替代方案是Playwright.
然而, 截止Playwright v1.16, 基於Playwright的測試仍然是實驗性功能, 並且在很大程度上不可用:
然而, 截止Playwright v1.16, 基於Playwright的測試仍然是實驗性功能, 並且在很大程度上不可用:
- 無法調試錯誤
- evaluate不支援導入Node.js模組
一個用於創建視窗覆蓋層的庫.
一站式開發和打包Electron程序.
一站式打包和分發Electron程序.
文件不一致到了讓人覺得可悲的地步.
Electron使用與Node.js不同版本的ABI, 因此無法直接使用npm和yarn以當前Node.js版本為目標下載的native模組,
electron-rebuild是解決這一問題的工具.
electron-rebuild是解決這一問題的工具.
出於可維護性方面的考慮, 不建議使用需要electron-rebuild的native模組.
- electron-rebuild無法選擇正確的ABI版本, 這使得依賴electron-rebuild的模組可能發展為一個難以升級的依賴項.
https://github.com/electron/electron-rebuild/issues/886 - 儘管屬於官方倉庫, electron-rebuild卻經常不能跟上Electron的最新版本.