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よりもはるかに人気があり、将来的にこの状況が変わる可能性は低いでしょう。
  • Electronはメインプロセスとレンダラープロセスを分離しています。
    レンダラープロセス内は2つのコンテキストに分かれており、それぞれNode.jsコードとブラウザ側コードを実行します。
Electronのモデルは関心事を分離していますが、この強制的な関心事分離の設計に意味があるかどうかは議論の余地があります。
異常に煩雑なIPC設計と絶え間ないセキュリティパフォーマンスは、開発体験を著しく悪化させます。
  • NW.jsはChrome Appsアーキテクチャに基づいており、Node.jsとChromiumレンダラーは同じプロセスを共有し、ブラウザ側で直接Node.jsモジュールを使用できます。
    ブラウザ側で直接Node.jsネイティブプラグインをロードできるため、NW.jsのパフォーマンスはIPCに依存するElectronやWASMベースのソリューションよりもはるかに優れています。
分離コンテキストと混合コンテキストの2つのモードがあります:
  • 分離コンテキストモードでは、NodeコンテキストとWebコンテキストの2つの環境に分かれます。NodeコンテキストはNode.jsモジュールの実行環境であり、WebコンテキストはNW.jsスクリプトの実行環境です。
    異なるコンテキストのグローバル変数は隔離されているため、参照等価性の問題が発生しやすいです。
  • 混合コンテキストモードでは、1つのコンテキストのみを使用します。
    参照等価性の問題は発生しませんが、サードパーティライブラリによってコンテキストが汚染されるリスクがあります。
  • Electronのエントリポイントはメインプロセスの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の現在のユーザー数は比較的少ないです。
    RPG Makerで作成されたゲームが、現在のNW.jsの最大のユーザー源である可能性があります。
NeutralinojsはChromiumをバンドルせず、オペレーティングシステム上のWebViewを使用します。
WebViewは当然、互換性の問題を引き起こすため、プロダクションレベルのソリューションとしては考慮されません。
Neutralinojsは古いWindowsシステムをサポートしていません: https://github.com/neutralinojs/neutralinojs/issues/486
TauriはRustとオペレーティングシステムのWebViewを使用し、Node.jsモジュールをサポートしておらず、APIをRustで記述する必要があります。
WebViewは当然、互換性の問題を引き起こすため、プロダクションレベルのソリューションとしては考慮されません。
デスクトッププラットフォームに加えて、TauriはiOSとAndroidもサポートする予定です。
  • コンテキスト隔離は不必要な複雑さを作り出します。
  • APIに統一性がなく、ウィンドウのメニューバーを削除するだけでも複数の方法があります。
  • メジャーバージョンアップの反復が速すぎます。
  • 有用なテストフレームワークが不足しています。
Electronアプリケーションのメインプロセスであり、アプリケーション全体のエントリポイントです。1つのインスタンスに1つしか存在できません。
Electronアプリケーション内でユーザーインターフェースを担当するプロセスで、メインプロセスによって起動されます。1つのインスタンスに複数存在できます。
レンダラープロセスは、デフォルトではネイティブのNode.jsモジュールに直接アクセスする能力を持っていません。
すべてのNode.jsモジュールは、preloadスクリプトによって作成されたコンテキスト隔離インターフェースを介して間接的に呼び出す必要があります。
ウィンドウ作成時に nodeIntegration: true オプションを介して、レンダラープロセスのNode.js統合を有効にできます。
デフォルトでは、preloadスクリプトとレンダラープロセスのホスト環境は隔離されています。
両者は contextBridge と window.postMessage を介してのみ通信できます。
contextIsolation: false を介してこの動作を無効にできます。
contextBridge.exposeInMainWorld は、preloadスクリプトでレンダラープロセスのホスト環境にAPIを公開する唯一の方法です。
ブリッジ時に利用できるデータ型は制限されています:
基本的な構造化クローン可能な型以外にも、関数、Promise、エラーなどのオブジェクトを送信できますが、一般的な要求を満たせない場合があります。
ブリッジには以下の特性もあります:
  • preloadにとって、API公開は一度限りであり、繰り返し公開することはできず、APIの変更をホスト環境に同期することもできません。
  • ホスト環境にとって、リモート関数内部はnative codeとして表示されます。
  • 公開されたオブジェクトは凍結されるため、ホスト環境にとってAPIは書き込み不可です。
  • サポートされていない型の場合、 undefined を渡す、オブジェクトのプロパティをコピーする、エラーを報告するという3つの異なる結果が発生する可能性があります。
レンダラープロセスはipcRendererを使用してメインプロセスにリクエストを発行できます。
このIPCは2つのモードをサポートしています:
  • send: ipcMainにメッセージを送信するのみ
  • invoke: リクエスト-リプライモード
コンテキスト隔離のため、レンダラープロセスで ipcRenderer.send/invoke を呼び出すのは非常に不便です。
メインプロセスはレンダラープロセスにリクエストを発行できますが、その前にレンダラープロセスのWebContentsオブジェクトを取得する必要があります。
WebContentsオブジェクトには以下の2つの取得方法があります:
  • レンダラーが最初にメインにリクエストを発行し、メインが受け取った event.sender がWebContentsオブジェクトになります。
  • メインがウィンドウ作成時のBrowserWindowを保持し、 BrowserWindow.webContents を使用します。
コンテキスト隔離のため、レンダラープロセスでイベントリスナーを登録するのは非常に不便です。
標準のMessagePortインターフェース。
https://www.electronjs.org/docs/latest/tutorial/message-ports
メインとレンダラー間、およびレンダラーとレンダラー間の通信に適用されます。
メインとレンダラー間はipcMainとipcRendererを介して通信できるため、
この通信方法は主にレンダラーとレンダラー間の通信に使用されます。
通信を確立する前に、まずipcMainとipcRendererを介してMessagePortオブジェクトを渡す必要があります。
ネットワークプロトコルを利用することで、HTTPやWebSocketのようなIPCを実装できます。
Electronでメインとレンダラー(Webコンテキスト)間の通信を確立することが非常に煩雑であることを考慮すると、
ネットワークプロトコルに基づくIPCを複雑性を軽減する方法として使用することも一つの手です。
  • IPCインターフェースは同じネットワーク上の他のプログラムに公開されます。
  • パフォーマンスは他の方法よりも劣ります。
ABIが異なるため、ElectronはNode.jsのネイティブモジュールを直接使用できません。モジュールは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://github.com/electron/electron/issues/32657
  • https://github.com/electron/electron/issues/33308
この実装はカスタム引数をサポートしていません。
設定項目をJSONファイルとして永続化するための専用ソリューションです。
内部では同じ作者のconfモジュールを使用しており、JSON Schemaと package.json に基づくバージョン移行をサポートしています。
メモリ内で動作するドキュメントデータベースで、間欠的にファイルシステムに永続化します。
開発は積極的ではありません。
JSONをデータベースとして使用する純粋なJavaScriptモジュールです。
ぶっちゃけ、これはJSONを読み書きするためのクラスであり、何の目立った点もありません。
作者はメンテナンスを停止しました。
RxJSとPouchDBを組み合わせた高度なデータベースです。
PouchDBのエコシステムを利用しているため、公式にElectronをサポートしています。
ダウンロード数とGithubスター数が不釣り合いな典型例で、実際のユーザー数は多くありません。
作者は多くの努力をこのプロジェクトに費やしていますが、新規ユーザーにとっては依然として学習曲線が急です。
このプロジェクトは、作者が制御できない多くのパッケージに依存しており、プロジェクトが非常に脆弱になっています:
  • RxJS
  • PouchDB
  • pouchdb-find(RxCollectionのfindメソッドはこのパッケージを直接使用しているため、同じ欠陥を共有します)
  • mquery(コードをコピー)
v10バージョン以降、RxDBはPouchDBからの独立準備を開始しました。
プロジェクトの人気度、保守性、およびその依存関係の信頼性のいずれから見ても、RxDBは使用する価値がありません。
Node.jsでは、PouchDBはlevelupモジュールを介してLevelDBをストレージバックエンドとして利用します。
levelupモジュールのバックエンドはネイティブモジュールですが、Electronをネイティブにサポートしているため、electron-rebuildで処理する必要なく使用できます。
PouchDBはマルチプロセスシナリオ向けに設計されておらず、多くのメモリベースの最適化があり、そのNode.jsバックエンドのLevelDBもマルチプロセスをサポートしていません。
したがって、Electronで使用する際は、メインプロセスでのみインスタンスを作成し、IPCを介してレンダラーにアクセスを公開する必要があります。
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は使用価値がありません。
前回のバージョンは2020年にリリースされ、それ以降のバグは修正されておらず、多くの重要なバグのissueがメンテナーによって無視されています。
その型定義 @types/pouchdb も古いです(PouchDB v6.4に対応)。
RxDBの作者もRxDB v10のリリースノートで、PouchDBが良好にメンテナンスされていないと述べています。
https://github.com/pouchdb/pouchdb/issues/3224
Node-API(N-API)をサポートしないネイティブモジュールであるため、
better-sqlite3はelectron-rebuildで処理した後にしか使用できず、これにより依存関係のロック問題が発生する可能性があります。
これはGhostチームがメンテナンスしているSQLite3モジュールです。
このモジュールはv5バージョンからNode-APIに基づいて書き直されました。
コールバックインターフェースはthisも設定しているため、アロー関数を使用できません。
Promiseラッパーを取得するには、サードパーティのラッパーnode-sqliteをインストールする必要があります。
このライブラリの各Databaseインスタンスは単なる接続であり、競合状態は存在しません。
トランザクションを開始すると、そのインスタンスで実行されるすべてのステートメントがそのトランザクションの一部となります。
そのため、トランザクションを開始するには常に新しいインスタンスを作成する必要があり、インスタンスを再利用するにはプールを作成する必要があるため、ソリューションが複雑になります。
これはVSCodeが使用しているSQLite3ライブラリで、sqlite3のフォークです。
https://www.electronjs.org/docs/latest/tutorial/application-distribution
本質的に、アプリケーションの配布は、特定のディレクトリ構造を満たすプリビルドされたElectronバイナリパッケージを配布することによって実現されます。
例えば、Windows上での手動配布方法は、対応するバージョン、対応するプラットフォームのプリビルドされたElectronバイナリパッケージをダウンロードし、
Electronディレクトリ内に resources/app ディレクトリを作成し、そこにプロジェクト全体のファイルをコピーします。
その後、通常はElectron実行ファイルの名前変更とアイコンの変更も必要になります。
公式のelectron-packagerパッケージは、上記の手順を簡素化するために使用できます。
Electron専用のtarのようなファイル形式で、Electronプロジェクトを単一のファイルにパッケージ化することを目的としています。
resources/app.asar ファイルは resources/app フォルダの代わりに使用できます。
一部のネイティブモジュールは、asarファイルにパッケージ化された後にこのエラーが表示されます。
asarパッケージを使用しない場合、Electronはネイティブモジュールを正しく見つけてロードできます。
electron-packagerの --asar を --asar.unpack=*.{node,dll} に置き換えることで、この問題を解決できます。
参照:
  • https://github.com/electron/packager/issues/424
  • https://github.com/electron/asar/issues/75
app.isPackaged は、Electronアプリケーションが開発環境にあるかプロダクション環境にあるかを判断するために広く使用されています。
しかし、その判断方法は直感に反しており、Electron実行ファイルの名前がelectronのままであるかどうかをチェックすることによって実現されます。
このパッケージは、各プラットフォームのアプリケーション作成を自動化するために使用されます。
  • 実行ファイルのアイコン設定をサポート。
  • パッケージサイズを縮小するためにdevDependenciesの削除をサポート。
electron-packagerには、ignoreファイルに基づいてパッケージパスを無視する機能がありません。かつて .electronignore ファイルのPRがありましたが、拒否されました
(拒否の理由は論理的・事実的根拠に欠けていました)。
この機能がないため、electron-packagerは役に立たないパッケージになってしまいます。なぜなら、コマンドラインで大量のignore引数を追加しない限り、開発環境も一緒にパッケージ化されてしまうからです。
このプロジェクトは、 --all オプションを有効にした場合、I/OとCPUリソースの使用量が実行するタスクに必要なレベルを明らかに超えています。
ソースコードを確認したところ、このプログラムはすべてのタスクを並列で実行しているだけであり、これはElectron v28では同時に10個のElectronをダウンロードすることを意味します。
サーバーの帯域幅とダウンロード側の帯域幅を考慮すると、このような無制限の並列処理はネットワークエラーに遭遇する確率を高めるだけです。
Windowsインストーラーのオープンソースソリューション。
electron-builderによってサポートされています。
コミュニティパッケージelectron-wix-msiによってサポートされています。
Windows 8からサポートされているUWP形式で、Microsoft Storeに公開できます。
electron-builderはMakeAppxツールを使用してプログラムをappxにパッケージ化できます。
Microsoftが2018年からサポートを開始した主要な形式で、MSIに代わることを意図しており、Windows 10以降のシステムで使用され、Microsoft Storeに公開できます。
  • ElectronコミュニティにはまだMSIXのパッケージングソリューションが存在しません。
  • パッケージングソリューションプロバイダーAdvanced Installerは、ElectronプロジェクトをMSIXにパッケージングすることをサポートしています。
  • Microsoftが提供する公式の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ベースのテストは依然として実験的な機能であり、大部分が利用できません:
  • エラーのデバッグができない
  • evaluateがNode.jsモジュールのインポートをサポートしていない
ウィンドウオーバーレイを作成するためのライブラリ。
Electronプログラムの開発とパッケージングをワンストップで提供します。
Electronプログラムのパッケージングと配布をワンストップで提供します。
ドキュメントの不整合が悲しいほどです。
ElectronはNode.jsとは異なるバージョンのABIを使用するため、npmやyarnで現在のNode.jsバージョンをターゲットとしてダウンロードされたネイティブモジュールを直接使用できません。
electron-rebuildはこの問題を解決するためのツールです。
メンテナンス性の観点から、electron-rebuildを必要とするネイティブモジュールの使用は推奨されません。
  • electron-rebuildは正しいABIバージョンを選択できず、electron-rebuildに依存するモジュールがアップグレード困難な依存関係になる可能性があります。
    https://github.com/electron/electron-rebuild/issues/886
  • 公式リポジトリに属しているにもかかわらず、electron-rebuildはElectronの最新バージョンに追いつけないことがよくあります。