メインコンテンツへスキップ

プロセスモデル

ElectronはChromiumからマルチプロセスのアーキテクチャを受け継いでおり、そのフレームワークは現代のウェブブラウザと非常に類似したアーキテクチャになっています。このガイドでは、チュートリアルで適用されている概念について詳しく説明します。

なぜ単一プロセスではないのか?

ウェブブラウザは非常に複雑なアプリケーションです。ウェブコンテンツを表示するという主要な機能以外にも、複数のウィンドウ(またはタブ)の管理やサードパーティ製拡張機能の読み込みなど、多くの二次的な役割を担っています。

初期のブラウザでは、通常、これらの機能すべてに単一のプロセスを使用していました。このパターンでは、開いているタブごとにオーバーヘッドが少なくなりましたが、一方、ウェブサイトのクラッシュやハングアップはブラウザ全体に影響を与えることを意味していました。

マルチプロセスモデル

この問題を解決するために、Chromeチームは、各タブをそれぞれ独自のプロセスでレンダリングすることにしました。これにより、ウェブページ上のバグのあるコードや悪意のあるコードがアプリケーション全体に与える悪影響を制限することができます。そして単一のブラウザプロセスがこれらのプロセスとアプリケーション全体のライフサイクルを制御します。Chrome Comicの以下の図はこのモデルを視覚的に表現しています。

Chrome's multi-process architecture

Electronアプリケーションも非常に似た構造になっています。アプリケーション開発者として、あなたはメインプロセスとレンダラプロセスの2種類のプロセスを制御します。これらは、上記で説明したChrome独自のブラウザプロセスとレンダラプロセスに相当します。

メインプロセス

各Electronアプリには、アプリケーションのエントリポイントとして機能する単一のメインプロセスがあります。メインプロセスはNode.js環境で実行されるため、モジュールのrequireやNode.js APIのすべてを使用できます。

ウィンドウ管理

メインプロセスの主な目的は、BrowserWindowモジュールを使用してアプリケーションウィンドウを作成および管理することです。

BrowserWindowクラスの各インスタンスは、別のレンダラプロセスでウェブページを読み込むアプリケーションウィンドウを作成します。ウィンドウのwebContentsオブジェクトを使用して、メインプロセスからこのウェブコンテンツとやり取りできます。

main.js
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を使用して、よりネイティブなアプリケーションウィンドウエクスペリエンスを実現しています。

main.js
// 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モジュールを直接含めるには、ウェブで使用しているものと同じバンドラーツールチェーン(たとえば、webpackparcel)を使用する必要があります。

警告

レンダラプロセスは、開発を容易にするために完全なNode.js環境で生成できます。歴史的にはこれがデフォルトでしたが、セキュリティ上の理由からこの機能は無効化されました。

この時点で、これらの機能がメインプロセスからのみアクセス可能な場合、レンダラプロセスのユーザーインターフェースがNode.jsとElectronのネイティブデスクトップ機能とどのように対話できるのか疑問に思うかもしれません。実際、Electronのコンテンツスクリプトを直接インポートする方法はありません。

プリロードスクリプト

プリロードスクリプトには、ウェブコンテンツの読み込みが始まる前にレンダラプロセスで実行されるコードが含まれています。これらのスクリプトはレンダラコンテキスト内で実行されますが、Node.js APIにアクセスできるため、より多くの権限が付与されます。

プリロードスクリプトは、BrowserWindowコンストラクタのwebPreferencesオプションでメインプロセスにアタッチできます。

main.js
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
// ...

プリロードスクリプトはレンダラとグローバルなWindowインターフェースを共有し、Node.js APIにアクセスできるため、ウェブコンテンツが使用できるwindowグローバルに任意のAPIを公開することで、レンダラを強化します。

プリロードスクリプトはアタッチされているレンダラとwindowグローバルを共有しますが、contextIsolationのデフォルトのため、プリロードスクリプトの変数をwindowに直接アタッチすることはできません。

preload.js
window.myAPI = {
desktop: true
}
renderer.js
console.log(window.myAPI)
// => undefined

コンテキスト分離とは、プリロードスクリプトがレンダラのメインワールドから分離され、特権APIがウェブコンテンツのコードに漏洩することを防ぐことを意味します。

代わりに、contextBridgeモジュールを使用して、安全にこれを実現します。

preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
desktop: true
})
renderer.js
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')