初めてのアプリのビルド
これは Electron チュートリアルの 2 章 です。
学習目標
このチュートリアルでは、Electron プロジェクトのセットアップと、最小限のスターターアプリケーションの作成方法を学びます。 この章の終わりには、ターミナルから開発モードで動作する Electron アプリを実行できるようになるでしょう。
プロジェクトのセットアップ
Windows マシンをご利用の方は、Windows Subsystem for Linux (WSL) を使用してアプリケーションを実行しようとすると問題が発生します。このチュートリアルに従う際にはご利用をお控えください。
npm プロジェクトの初期化
Electron アプリは npm を利用して組み上げられ、package.json ファイルをエントリポイントとします。 まずフォルダを作成し、その中で npm init
を実行して npm パッケージを初期化します。
- npm
- Yarn
mkdir my-electron-app && cd my-electron-app
npm init
mkdir my-electron-app && cd my-electron-app
yarn init
このコマンドは、package.json のいくつかのフィールドに対する設定を確認します。 このチュートリアルの目的上、以下のルールに従ってください。
- エントリポイント は
main.js
にしてください (後でそのファイルを作成します)。 - author、license、description は何にでもできますが、後の パッケージ化 において必要となります。
次に、Electron をアプリのdevDependencies にインストールします。これは、本番環境では必要ない外部の開発専用パッケージの依存関係のリストです。
これは本番環境のコードで Electron API を実行していることから、直感に反していると思われるかもしれません。 しかし、パッケージ化されたアプリには Electron のバイナリがバンドルされるため、本番環境時の依存関係として指定しないでください。
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
パッケージを初期化して Electron をインストールすると、package.json ファイルは以下のようになるでしょう。 さらに Electron の実行形式などが含まれた node_modules
フォルダに加えて、インストールする正確な依存関係のバージョンを指定する package-lock.json
ロックファイルも生成されるはずです。
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
Electron を直接インストールできない場合は、発展的なインストール でダウンロードミラー、プロキシ、トラブルシューティングの手順について説明しています。
.gitignore の追加
.gitignore
ファイルでは、Git での追跡を避けるファイルやディレクトリを指定します。 GitHub の Node.js gitignore テンプレート をコピーしてプロジェクトのルートフォルダに配置しておけば、プロジェクトの node_modules
フォルダのコミットを回避できます。
Electron アプリの実行
Electron のプロセスモデル のドキュメントを一読いただければ、Electron の複数のプロセスがどのように連携して動作するかがよりよく理解できるでしょう。
どのような Electron アプリケーションのエントリポイントも、package.json で定義した main
スクリプトになります。 このスクリプトは メインプロセス を制御します。メインプロセスは Node.js 環境で動作し、アプリのライフサイクル制御、ネイティブインターフェースの表示、特権操作、レンダラープロセス (後述) の管理を担います。
最初の Electron アプリを作成する前に、まず小さなスクリプトを使用して、メインプロセスのエントリポイントが正しく設定されていることを確認します。 プロジェクトのルートフォルダに main.js
というファイルを作成し、以下の 1 行を記述します。
console.log('Hello from Electron 👋')
Electron のメインプロセスは Node.js ランタイムなので、任意の Node.js コードを electron
コマンド (これは REPL としても利用可能) で実行できます。 このスクリプトを実行するには、package.json のscripts
フィールド内に start
コマンドとして electron .
を追加します。 このコマンドは、Electron の実行形式に対して、カレントディレクトリにある main スクリプトを探して開発モードで実行するように指示します。
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
- npm
- Yarn
npm run start
yarn run start
ターミナルには Hello from Electron 👋
と出力されるはずです。 おめでとうございます、これにより Electron で初めてコードを 1 行実行しました! 次に、HTML でユーザーインターフェースを作成し、それをネイティブウインドウへ読み込む方法を学びます。
ウェブページを BrowserWindow で読み込む
Electron では、各ウインドウにウェブページが表示され、これはローカルの HTML ファイルまたはリモートのウェブアドレスから読み込むことができます。 このサンプルでは、ローカルファイルを読み込んでみましょう。 まず、プロジェクトのルートフォルダに index.html
ファイルを作成し、以下の基本的なウェブページを作成することから始めます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
これでウェブページを Electron の BrowserWindow に読み込ませることができました。 main.js
ファイルの内容を以下のコードに置き換えてください。 ハイライトしたブロックをそれぞれ別々に解説します。
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
モジュールのインポート
const { app, BrowserWindow } = require('electron')
1 行目では、CommonJS のモジュール構文で 2 つの Electron モジュールをインポートしています。
- app、アプリケーションのイベントライフサイクルを制御します。
- BrowserWindow、アプリのウインドウを作成し管理します。
app モジュールと BrowserWindow モジュールの大文字小文字の規則の違いにお気づきでしょうか。 ここでは Electron は典型的な JavaScript の慣習に従っています。PascalCase のモジュールはインスタンス化可能なクラスコンストラクタ (例: BrowserWindow、Tray、Notification) であるのに対し、camelCase のモジュールはインスタンス化できません (例: app、ipcRenderer、webContents)。
ECMAScript modules (例えば import
によるモジュールの読み込み) は現在、Electron では直接サポートされていません。 Electron の ESM についての状態は、electron/electron#21457 に詳しい情報があります。
ウインドウを作成する再使用可能な関数を書く
この createWindow()
関数は、ウェブページを新しい BrowserWindow インスタンスで読み込みます。
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
アプリの準備ができたら関数を呼び出す
app.whenReady().then(() => {
createWindow()
})
Electron のコアモジュールの多くは Node.js の EventEmitter で、Node の非同期の イベント駆動型アーキテクチャに準拠しています。 app モジュールはこれらエミッターのうちの 1 つです。
Electron では、BrowserWindow は app モジュールの ready
イベントが発生した後でのみ作成可能です。 このイベントを待機するには、app.whenReady()
API を利用してその Promise が履行されたときに createWindow()
を一回呼ぶことで可能です。
Node.js のイベントは通常、エミッタの .on
関数を用いてリッスンします。
+ app.on('ready', () => {
- app.whenReady().then(() => {
createWindow()
})
しかし, Electron は ready
イベントのヘルパーとして app.whenReady()
を公開しています。これは直接リッスンすることによる些細なミスを回避するためのものです。 詳細は electron/electron#21972 をご参照ください。
この時点で、Electron アプリケーションの start
コマンドを実行すると、ウェブページを表示するウインドウが正常に開くでしょう。
アプリがウインドウに表示する各ウェブページは、レンダラー プロセスという (または単に レンダラー と略す) 個別のプロセスで実行されます。 レンダラープロセスは典型的なフロントエンドのウェブ開発と同じように JavaScript の API へアクセスでき、同じツール、例えば webpack によるコードのバンドルと Minify、React によるユーザーインターフェイスの構築などが利用できます。
アプリのウインドウのライフサイクル管理
アプリケーションウインドウは、オペレーティングシステムによって動作が異なります。 Electron はそれぞれの慣習をデフォルトでは強制せず、慣習に従いたい場合はアプリのコードで実装する選択肢を提供します。 app モジュールと BrowserWindow モジュールが発生するイベントをリッスンすることで、基本的なウインドウの慣習的動作を実装できます。
Node の process.platform
変数を確認することで、特定プラットフォームの条件下でコードを実行できます。 注意として、取りうるプラットフォームの値は Electron が実行できる win32
(Windows)、linux
(Linux)、darwin
(macOS) の 3 つのみです。
全ウインドウを閉じた時にアプリを終了する (Windows & Linux)
一般的に Windows や Linux では、すべてのウインドウを閉じるとアプリケーションが完全に終了します。 Electron アプリでこのパターンを実装するには、app モジュールの window-all-closed
イベントをリッスンし、ユーザが macOS でなければ app.quit()
を呼び出してアプリを終了します。
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
開いたウインドウがない場合にウインドウを開く (macOS)
一方、macOS アプリは一般的に、ウインドウを開いていなくても動作し続けます。 ウインドウがないときにアプリをアクティブにすると、新規ウインドウが開かれるでしょう。
この機能を実装するには、app モジュールの activate
イベントをリッスンし、BrowserWindow が開かれていなければ既存の createWindow()
メソッドを呼び出します。
ready
イベントの前ではウインドウを作成できないので、アプリが初期化された後に activate
イベントだけをリッスンする必要があります。 これは既存の whenReady()
コールバック内で activate イベントをリッスンすることでのみ実現できます。
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
最終的なのスターターコード
- main.js
- index.html
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
任意: VS Code からのデバッグ
VS Code を使用してアプリケーションをデバッグしたい場合、VS Code をメインプロセスとレンダラープロセスの両方にアタッチする必要があります。 こちらは実行するためのサンプル構成です。 プロジェクトのフォルダ内に新しく .vscode
フォルダを作成し、その中に以下の launch.json を作成してください。
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}
サイドバーから「実行とデバッグ」を選択すると「Main + renderer」オプションが表示され、メインとレンダラーの両方のプロセスでブレークポイントの設定やすべての変数の検査などができるようになります。
この launch.json
ファイルでは、以下 3 つの構成を作成しています。
Main
はメインプロセスを起動するもので、ポート 9222 をリモートデバッグのために公開します (--remote-debugging-port=9222
)。 これはRenderer
にアタッチするために使用するデバッガのポートです。 メインプロセスは Node.js プロセスであるため、タイプはnode
に設定します。Renderer
レンダラープロセスをデバッグするものです。 メインプロセスはレンダラープロセスを作成するものなので、新しいプロセスを作成する代わりにそれへと「アタッチ」("request": "attach"
) する必要があります。 レンダラープロセスはウェブのプロセスなので、使用するデバッガはchrome
となります。Main + renderer
は 複合タスク で、先程の構成すべてを同時に実行します。
Renderer
でプロセスにアタッチしているため、コードの最初の行が実行される前にデバッガーが接続しようとすると、早すぎてスキップされることがあります。 これは開発モードでコードを実行する前に、ページを更新したりタイムアウトを設定することで回避できます。
デバッグ分野をより深く掘り下げたい場合は、以下のガイドに詳しい情報が掲載されています。
概要
Electron アプリケーションは、npm パッケージを使用してセットアップされます。 Electron の実行形式はプロジェクトの devDependencies
にインストールされている必要があり、package.json ファイル内のスクリプトを用いて開発モードで実行できます。
この実行形式は package.json の main
プロパティにある JavaScript のエントリポイントを実行します。 このファイルは Electron の メインプロセス を制御します。メインプロセスは Node.js のインスタンスを実行し、アプリのライフサイクル、ネイティブインターフェースの表示、特権操作、レンダラープロセスの管理を担います。
レンダラープロセス (または略してレンダラー) はグラフィカルなコンテンツの表示を担います。 レンダラーにウェブページを読み込むには、ウェブアドレスまたはローカルの HTML ファイルを指定します。 レンダラーの動作は通常のウェブページと非常に似ており、同じウェブの API にアクセスできます。
次章のチュートリアルでは、レンダラープロセスを特権 API で拡張する方法と、プロセス間の通信方法について学習します。