初めてのアプリのビルド
これは 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, and _description_はどんな値でも構いませんが、後で パッケージ化(packaging) する際に必要になります。
::: caution 依存するライブラリなどは通常の node_modules フォルダに保存してください
Electronのパッケージングツールチェーンを使用するには、通常のnpmがnodeの依存性をインストールする際と同様に、node_modules フォルダが物理的にディスク上に存在する必要があります。 デフォルト設定では, Yarn Berryと pnpmは共に、代替的な依存性の管理戦略を取ります。
したがって、Yarnを使用する場合はnodeLinker: node-modulesを、pnpmを使用する場合はnodeLinker: hoistedを設定してください。
:::
次に、Electron をアプリのdevDependencies にインストールします。これは、本番環境では必要ない外部の開発専用パッケージの依存関係のリストです。
これは本番環境のコードで Electron API を実行していることから、直感に反していると思われるかもしれません。 しかし、実際にはJavaScriptで提供されるElectronAPIは、binaryとして実装され提供されます。 Electronは、パッケージング時にこれらのbinaryをアプリケーションに組み込むため、production用の依存性としてElectronを含める必要がないのです。
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
Electronのインストールを正しく完了するためには、ライフサイクルのpostinstallスクリプトが実行可能である必要があります。 したがって、npmを使用する際は --ignore-scriptsフラグの使用は避け、また他のパッケージマネージャを使用する際はelectronがビルドスクリプトを実行できるように許可してください。
This is likely to change in a future version of Electron. See electron/rfcs#22 for more details.
パッケージを初期化して 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のランタイムで実行されるため、 electronコマンドを通じて任意のNode.jsコマンドを実行することができます。( 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)。
型付きの import エイリアス
TypeScript コードを記述する際の型検査を改善するために、electron/main からメインプロセスのモジュールをインポートする選択ができます。
const { app, BrowserWindow } = require('electron/main')
For more information, see the Process Model docs.
Electron 28 から、Electron での ECMAScript Modules (例えば import によるモジュールの読み込み) がサポートされています。 Electron での ESM の状態についてさらなる情報は、私たちの ESM のガイド にあります。
ウインドウを作成する再使用可能な関数を書く
この 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のBrowserWindowsはappモジュールがreadyイベントを発火した後にのみ作成することができます。 app.whenReady() APIを使用しreadyイベントが発火されるのを待ち、返り値の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をlistenし、ユーザーがmacOSを使用していない場合は app.quit()を呼び出すことでアプリを終了させます。
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
開いたウインドウがない場合にウインドウを開く (macOS)
一方、macOS アプリは一般的に、ウインドウを開いていなくても動作し続けます。 ウインドウがないときにアプリをアクティブにすると、新規ウインドウが開かれるでしょう。
この機能を実装するには、appモジュールが 発火するactivateイベントをlistenし、もし一つもBrowserWindowsが開いていない場合は、先に実装した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/main')
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 で拡張する方法と、プロセス間の通信方法について学習します。