プロセスモデル
ElectronはChromiumからマルチプロセスのアーキテクチャを受け継いでおり、そのフレームワークは現代のウェブブラウザと非常に類似したアーキテクチャになっています。このガイドでは、チュートリアルで適用されている概念について詳しく説明します。
なぜ単一プロセスではないのか?
ウェブブラウザは非常に複雑なアプリケーションです。ウェブコンテンツを表示するという主要な機能以外にも、複数のウィンドウ(またはタブ)の管理やサードパーティ製拡張機能の読み込みなど、多くの二次的な役割を担っています。
初期のブラウザでは、通常、これらの機能すべてに単一のプロセスを使用していました。このパターンでは、開いているタブごとにオーバーヘッドが少なくなりましたが、一方、ウェブサイトのクラッシュやハングアップはブラウザ全体に影響を与えることを意味していました。
マルチプロセスモデル
この問題を解決するために、Chromeチームは、各タブをそれぞれ独自のプロセスでレンダリングすることにしました。これにより、ウェブページ上のバグのあるコードや悪意のあるコードがアプリケーション全体に与える悪影響を制限することができます。そして単一のブラウザプロセスがこれらのプロセスとアプリケーション全体のライフサイクルを制御します。Chrome Comicの以下の図はこのモデルを視覚的に表現しています。
Electronアプリケーションも非常に似た構造になっています。アプリケーション開発者として、あなたはメインプロセスとレンダラプロセスの2種類のプロセスを制御します。これらは、上記で説明したChrome独自のブラウザプロセスとレンダラプロセスに相当します。
メインプロセス
各Electronアプリには、アプリケーションのエントリポイントとして機能する単一のメインプロセスがあります。メインプロセスはNode.js環境で実行されるため、モジュールのrequire
やNode.js APIのすべてを使用できます。
ウィンドウ管理
メインプロセスの主な目的は、BrowserWindow
モジュールを使用してアプリケーションウィンドウを作成および管理することです。
BrowserWindow
クラスの各インスタンスは、別のレンダラプロセスでウェブページを読み込むアプリケーションウィンドウを作成します。ウィンドウのwebContents
オブジェクトを使用して、メインプロセスからこのウェブコンテンツとやり取りできます。
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
注:
BrowserView
モジュールなどのウェブ埋め込みに対してもレンダラプロセスが作成されます。埋め込まれたウェブコンテンツに対してもwebContents
オブジェクトにアクセスできます。
BrowserWindow
モジュールはEventEmitter
であるため、さまざまなユーザーイベント(ウィンドウの最小化や最大化など)のハンドラーを追加することもできます。
BrowserWindow
インスタンスが破棄されると、対応するレンダラプロセスも終了します。
アプリケーションライフサイクル
メインプロセスは、Electronのapp
モジュールを通じてアプリケーションのライフサイクルも制御します。このモジュールは、カスタムアプリケーション動作を追加するために使用できる多数のイベントとメソッドを提供します(たとえば、プログラムによってアプリケーションを終了したり、アプリケーションドックを変更したり、バージョン情報パネルを表示したりなど)。
クイックスタートガイドに示されているアプリは、app
APIを使用して、よりネイティブなアプリケーションウィンドウエクスペリエンスを実現しています。
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
ネイティブAPI
Electronの機能をウェブコンテンツのChromiumラッパーを超えて拡張するために、メインプロセスはユーザーのオペレーティングシステムと対話するためのカスタムAPIも追加します。Electronは、メニュー、ダイアログ、トレイアイコンなど、ネイティブデスクトップ機能を制御するさまざまなモジュールを公開しています。
Electronのメインプロセスモジュールの完全なリストについては、APIドキュメントをご覧ください。
レンダラプロセス
各Electronアプリは、開いている各BrowserWindow
(および各ウェブ埋め込み)に対して個別のレンダラプロセスを生成します。その名前が示すように、レンダラはウェブコンテンツのレンダリングを担当します。あらゆる点において、レンダラプロセスで実行されるコードは、ウェブ標準に従って動作する必要があります(少なくともChromiumがそうである限り)。
したがって、単一のブラウザウィンドウ内のすべてのユーザーインターフェースとアプリ機能は、ウェブで使用しているものと同じツールとパラダイムを使用して記述する必要があります。
このガイドではすべてのウェブ仕様を説明することはできませんが、理解する上で最低限必要なのは以下のとおりです。
- HTMLファイルはレンダラプロセスのエントリポイントです。
- UIスタイルはカスケーディングスタイルシート(CSS)を使用して追加されます。
- 実行可能なJavaScriptコードは
<script>
要素を使用して追加できます。
さらに、これはレンダラがrequire
やその他のNode.js APIに直接アクセスできないことも意味します。レンダラにNPMモジュールを直接含めるには、ウェブで使用しているものと同じバンドラーツールチェーン(たとえば、webpack
やparcel
)を使用する必要があります。
レンダラプロセスは、開発を容易にするために完全なNode.js環境で生成できます。歴史的にはこれがデフォルトでしたが、セキュリティ上の理由からこの機能は無効化されました。
この時点で、これらの機能がメインプロセスからのみアクセス可能な場合、レンダラプロセスのユーザーインターフェースがNode.jsとElectronのネイティブデスクトップ機能とどのように対話できるのか疑問に思うかもしれません。実際、Electronのコンテンツスクリプトを直接インポートする方法はありません。
プリロードスクリプト
プリロードスクリプトには、ウェブコンテンツの読み込みが始まる前にレンダラプロセスで実行されるコードが含まれています。これらのスクリプトはレンダラコンテキスト内で実行されますが、Node.js APIにアクセスできるため、より多くの権限が付与されます。
プリロードスクリプトは、BrowserWindow
コンストラクタのwebPreferences
オプションでメインプロセスにアタッチできます。
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
// ...
プリロードスクリプトはレンダラとグローバルなWindow
インターフェースを共有し、Node.js APIにアクセスできるため、ウェブコンテンツが使用できるwindow
グローバルに任意のAPIを公開することで、レンダラを強化します。
プリロードスクリプトはアタッチされているレンダラとwindow
グローバルを共有しますが、contextIsolation
のデフォルトのため、プリロードスクリプトの変数をwindow
に直接アタッチすることはできません。
window.myAPI = {
desktop: true
}
console.log(window.myAPI)
// => undefined
コンテキスト分離とは、プリロードスクリプトがレンダラのメインワールドから分離され、特権APIがウェブコンテンツのコードに漏洩することを防ぐことを意味します。
代わりに、contextBridge
モジュールを使用して、安全にこれを実現します。
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
desktop: true
})
console.log(window.myAPI)
// => { desktop: true }
この機能は、主に以下の2つの目的で非常に役立ちます。
ipcRenderer
ヘルパーをレンダラに公開することにより、プロセス間通信(IPC)を使用して、レンダラからメインプロセスタスクをトリガーできます(その逆も同様です)。- リモートURLでホストされている既存のウェブアプリのElectronラッパーを開発している場合、ウェブクライアント側でデスクトップ専用のロジックに使用できるカスタムプロパティをレンダラの
window
グローバルに追加できます。
ユーティリティプロセス
各Electronアプリは、UtilityProcess APIを使用して、メインプロセスから複数の子プロセスを生成できます。ユーティリティプロセスはNode.js環境で実行されるため、モジュールのrequire
やNode.js APIのすべてを使用できます。ユーティリティプロセスは、たとえば、信頼できないサービス、CPU集約的なタスク、またはクラッシュしやすいコンポーネント(以前はメインプロセスでホストされていたか、Node.jsのchild_process.fork
APIで生成されたプロセスでホストされていたもの)のホストに使用できます。ユーティリティプロセスとNode.jsのchild_processモジュールによって生成されたプロセスの主な違いは、ユーティリティプロセスがMessagePort
を使用してレンダラプロセスと通信チャネルを確立できることです。Electronアプリでは、メインプロセスから子プロセスをフォークする必要がある場合、常にNode.jsのchild_process.fork
APIよりもUtilityProcess APIを優先できます。
プロセス固有のモジュールエイリアス(TypeScript)
Electronのnpmパッケージは、ElectronのTypeScript型定義のサブセットを含むサブパスもエクスポートします。
electron/main
には、すべてのメインプロセスモジュールの型が含まれています。electron/renderer
には、すべてのレンダラプロセスモジュールの型が含まれています。electron/common
には、メインプロセスとレンダラプロセスの両方で実行できるモジュールの型が含まれています。
これらのエイリアスは実行時に影響を与えませんが、型チェックとオートコンプリートに使用できます。
const { app } = require('electron/main')
const { shell } = require('electron/common')