プロセスモデル
Electron は Chromium のマルチプロセスアーキテクチャを継承しており、フレームワークのアーキテクチャは最新のウェブブラウザに酷似しています。 This guide will expand on the concepts applied in the Tutorial.
なぜシングルプロセスではないのでしょうか?
ウェブブラウザは非常に複雑なアプリケーションです。 ウェブコンテンツを表示するという主な機能のほかに、複数のウィンドウ (またはタブ) を管理したり、サードパーティの拡張機能を読み込んだりするなど、多くの副次的な役割を担っています。
以前のブラウザでは、これらの機能を単一のプロセスで実現していました。 このやり方は開いているタブごとのオーバーヘッドを減らしますが、1 つのウェブサイトがクラッシュしたりハングアップしたりすると、ブラウザ全体に影響が及びます。
マルチプロセスモデル
この問題を解決するため、Chrome チームは各タブがそれぞれのプロセスで描画するようにすると決め、ウェブページ上のバグや悪意のあるコードがアプリ全体に与える影響を制限することにしました。 単一のブラウザプロセスはこれらのプロセスを制御し、アプリケーションのライフサイクル全体を制御します。 このモデルを視覚化したのが、Chrome 漫画本 の以下の図です。
Electron アプリケーションも非常によく似た構造をしています。 あなたはアプリ開発者として、メイン と レンダラー の 2 種類のプロセスを制御することになります。 これらは、上述の Chrome 独自のブラウザプロセスとレンダラープロセスと似ています。
メインプロセス
各 Electron アプリにつき一つのメインプロセスがあります。これはアプリケーションのエントリポイントとして機能します。 メインプロセスは Node.js 環境で動作します。つまり、モジュールを require したり Node.js のすべての API を利用したりできます。
ウインドウの管理
The main process' primary purpose is to create and manage application windows with the BrowserWindow module.
BrowserWindow クラスの各インスタンスは、アプリケーションウインドウを作成し、その分かれたレンダラープロセス内でウェブページを読み込みます。 You can interact with this web content from the main process using the window's webContents object.
const{BrowserWindow}=require('electron')
const win =newBrowserWindow({width:800,height:1500})
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
[!NOTE] A renderer process is also created for web embeds such as the
BrowserViewmodule.webContentsオブジェクトは、埋め込みウェブコンテンツにもアクセスできます。
BrowserWindow モジュールは EventEmitter を継承しているため、様々なユーザーイベント (例えば、ウインドウの最小化や最大化) ハンドラの追加もできます。
BrowserWindow インスタンスが破棄されると、対応するレンダラープロセスも終了します。
アプリケーションのライフサイクル
The main process also controls your application's lifecycle through Electron's app module. このモジュールには、アプリケーションの動作をカスタマイズするためのイベントやメソッドが多数用意されています (例えば、プログラム側でアプリケーションを終了したり、アプリケーションの Dock を変更したり、アプリについてのパネルを表示したりできます)。
As a practical example, the app shown in the tutorial starter code uses app APIs to create a more native application window experience.
// 非 macOS プラットフォームでウインドウが開かれていない時にアプリを終了する
app.on('window-all-closed',()=>{
if(process.platform!=='darwin') app.quit()
})
ネイテイブ API
ウェブコンテンツ用の Chromium ラッパーだけでなく Electron の機能を拡張するため、メインプロセスではユーザーのオペレーティングシステムと対話するカスタム API も追加しています。 Electron は、メニュー、ダイアログ、tray アイコンなど、ネイティブなデスクトップ機能を制御する様々なモジュールを公開しています。
Electron のメインプロセスのモジュール一覧は、API ドキュメントをご覧ください。
レンダラープロセス
各 Electron アプリは、開いている BrowserWindow (及び各ウェブ埋め込み) ごとに個別のレンダラープロセスを生成します。 その名の通り、レンダラーはウェブコンテンツの レンダリング を担います。 あらゆる意図と目的において、レンダラープロセスで実行するコードは (少なくとも Chromium がそうである限り) ウェブ標準に従って動作しなければなりません。
そのため、あるブラウザウインドウ内のすべてのユーザーインターフェイスとアプリの機能は、ウェブの場合と同じツールとパラダイムで記述する必要があります。
全ウェブ仕様の説明はこのガイドの範疇の外ですが、最低限理解しておくべきことは以下の通りでしょう。
- HTML ファイルがレンダラープロセスのエントリーポイントです。
- UI のスタイル付けは Cascading Style Sheets (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 =newBrowserWindow({
webPreferences:{
preload:'path/to/preload.js'
}
})
// ...
プリロードスクリプトは、グローバルな Window  インターフェイスをレンダラーと共有し Node.js の API にアクセスすることができます。そのため、window グローバルに任意の API を公開してウェブコンテンツが利用できるようにすることで、レンダラーを強化する役割を果たしています。
Although preload scripts share a window global with the renderer they're attached to, you cannot directly attach any variables from the preload script to window because of the contextIsolation default.
window.myAPI={
desktop:true
}
console.log(window.myAPI)
// => undefined
コンテキスト分離 (contextIsolation) とは、プリロードスクリプトをレンダラーのメインワールドから分離し、特権的 API がウェブコンテンツのコードへ漏れないようにすることです。
Instead, use the contextBridge module to accomplish this securely:
const{ contextBridge }=require('electron')
contextBridge.exposeInMainWorld('myAPI',{
desktop:true
})
console.log(window.myAPI)
// => { desktop: true }
この機能は、主に以下に挙げる 2 つの目的において非常に便利です。
- By exposing ipcRendererhelpers to the renderer, you can use inter-process communication (IPC) to trigger main process tasks from the renderer (and vice-versa).
- リモート URL でホストされている既存ウェブアプリの Electron のラッパーを開発している場合、レンダラーの windowグローバルにカスタムプロパティを追加することで、ウェブクライアント側でデスクトップ専用のロジックを利用できます。
ユーティリティプロセス
Each Electron app can spawn multiple child processes from the main process using the UtilityProcess API. ユーティリティプロセスは Node.js 環境で動作します。つまり、モジュールを require したり Node.js のすべての API を利用したりできます。 ユーティリティプロセスは例えば、信頼できないサービス、CPU 負荷の高いタスク、クラッシュしやすいコンポーネントなど、以前はメインプロセスや Node.js の child_process.fork API でスポーンしていたプロセスのホストに利用できます。 Node.js の child_process モジュールが生成するプロセスとユーティリティプロセスとの主な違いは、ユーティリティプロセスが MessagePort でレンダラープロセスと通信チャンネルを確立できることです。 An Electron app can always prefer the UtilityProcess API over Node.js child_process.fork API when there is need to fork a child process from the main process.
プロセス固有のモジュールエイリアス (TypeScript)
Electron の npm パッケージは、Electron の TypeScript 型定義のサブセットを含むサブパスもエクスポートしています。
- electron/mainは全てのメインプロセスのモジュールについての型を含んでいます。
- electron/rendererは全てのレンダラープロセスのモジュールについての型を含んでいます。
- electron/commonはメインとレンダラープロセスで実行できるモジュールの型を含んでいます。
これらのエイリアスは実行時には影響しませんが、タイプチェックやオートコンプリートは使用できます。
const{ shell }=require('electron/common')
const{ app }=require('electron/main')