【初心者向け】Electron + Reactで始めるデスクトップアプリ開発
Web開発のスキルを使って、Windows、macOS、Linux上で動く本格的なデスクトップアプリケーションを作ってみたいと思ったことはありませんか? そんな願いを叶える強力なツールが「Electron」と「React」の組み合わせです。
この記事では、JavaScriptやWeb開発の経験がある方を対象に、ElectronとReactを使ってデスクトップアプリ開発を始めるための基礎知識から具体的なプロジェクトの構築方法、ビルド、配布までを、ステップバイステップで詳細に解説します。約5000語をかけて、この魅力的な組み合わせの世界へご案内します。
さあ、あなたのWeb開発スキルをデスクトップの世界へ広げましょう!
1. はじめに:デスクトップアプリ開発への第一歩
普段WebサイトやWebアプリケーションを開発している皆さんにとって、デスクトップアプリケーション開発は少し敷居が高いと感じるかもしれません。Java、C#、C++、Swift、Objective-Cなど、特定のプログラミング言語やフレームワークを学ぶ必要があると思われがちです。
しかし、「Electron」というツールが登場したことで、状況は大きく変わりました。Electronを使えば、あなたが慣れ親しんだJavaScript、HTML、CSSといったWeb技術スタックで、クロスプラットフォーム対応のデスクトップアプリケーションを開発できるのです。
そして、ユーザーインターフェース(UI)を構築する上で、宣言的で効率的なライブラリである「React」は非常に相性が良いパートナーです。多くのWeb開発者がUI構築に使っているReactをそのままデスクトップアプリにも活かせるのです。
この記事では、ElectronとReactの組み合わせがなぜ強力なのかを説明し、実際に簡単なデスクトップアプリケーションを作成しながら、その基本的な開発プロセスを習得することを目指します。
この記事で学ぶこと:
- Electronの基本的な仕組み(メインプロセスとレンダラープロセス)
- ElectronとReactを組み合わせるためのプロジェクト構成
- 開発環境のセットアップ方法
- ElectronとReact間でのデータ通信(IPC)
- アプリケーションのビルドと配布方法
- 開発におけるヒントと注意点
さあ、準備はいいですか? デスクトップアプリ開発の旅を始めましょう!
2. ElectronとReact:なぜこの組み合わせなのか?
ElectronとReact、それぞれがどのような技術で、なぜ組み合わせるのが効果的なのかを見ていきましょう。
2.1 Electronの魅力と基本的な仕組み
Electronは、GitHubによって開発されたオープンソースのフレームワークです。Web技術を使って、クロスプラットフォーム(Windows, macOS, Linux)のデスクトップアプリケーションを構築できます。Slack、Visual Studio Code、Discord、Figma、Notionなど、多くの人気アプリケーションがElectronで開発されています。
Electronの核となっているのは、Webブラウザのレンダリングエンジンである「Chromium」と、JavaScriptの実行環境である「Node.js」です。
- Chromium: HTML、CSS、JavaScriptを解析・実行し、UIを表示する部分を担当します。これにより、Webページを表示するようにアプリケーションのUIを構築できます。
- Node.js: JavaScriptを使ってファイルシステムへのアクセス、ネットワーク通信、子プロセスの実行など、OSレベルの操作を行う部分を担当します。これにより、Webブラウザではできないデスクトップアプリケーション特有の機能(ファイルの読み書き、ネイティブメニューの操作など)を実現できます。
Electronアプリケーションは、大きく分けて二つのプロセスで構成されます。
-
メインプロセス (Main Process):
- アプリケーションのエントリポイント(通常は
main.js
のようなファイル)です。 - Node.js環境で実行されます。
- アプリケーションのライフサイクル(起動、終了など)を管理します。
- ウィンドウ(
BrowserWindow
インスタンス)を作成・管理します。 - OSのネイティブ機能(メニューバー、ダイアログ、通知など)にアクセスします。
- ファイルシステムやネットワークなど、Node.jsのAPIに直接アクセスできます。
- アプリケーション内で一つだけ存在します。
- アプリケーションのエントリポイント(通常は
-
レンダラープロセス (Renderer Process):
BrowserWindow
インスタンスごとに生成されます。Webブラウザのタブやウィンドウに相当します。- Chromium環境で実行されます。
- Webページ、つまりアプリケーションのUIを表示します。HTML、CSS、JavaScript(Reactコードなど)がここで実行されます。
- 標準ではNode.js APIに直接アクセスできません(セキュリティのため)。
- 複数のウィンドウを開けば、複数のレンダラープロセスが存在します。
メインプロセスとレンダラープロセスの関係:
メインプロセスはアプリケーション全体を管理し、レンダラープロセスは各ウィンドウのUIを担当します。両プロセスは、IPC (Inter-Process Communication) と呼ばれる仕組みを使って互いに通信します。例えば、レンダラープロセスでボタンがクリックされたときにファイルを保存したい場合、レンダラープロセスはIPCを使ってメインプロセスに「ファイルを保存してほしい」と依頼し、メインプロセスがNode.js APIを使って実際にファイルを保存する、という流れになります。
このメイン/レンダラーの分離が、Electronアプリケーションの構造を理解する上で最も重要です。
2.2 Reactの魅力とWeb開発との関連性
Reactは、Facebook(現Meta)によって開発されたJavaScriptのUI構築ライブラリです。その最大の魅力は、宣言的な方法でUIを構築できること、コンポーネント指向であること、そして効率的な描画を実現する仮想DOMです。
- 宣言的UI: ユーザーインターフェースの状態を定義するだけで、Reactが自動的にその状態を反映するようにUIを更新してくれます。手動でDOM要素を操作する手間が省け、コードがシンプルで予測しやすくなります。
- コンポーネント指向: UIを独立した再利用可能な部品(コンポーネント)に分割して開発できます。これにより、コードの管理が容易になり、保守性や拡張性が向上します。
- 仮想DOM (Virtual DOM): UIの変更を直接ブラウザのDOMに適用するのではなく、一度仮想DOM上で計算し、最小限の差分だけを実際のDOMに反映します。これにより、パフォーマンスの高いUI描画が可能になります。
Reactは元々Webブラウザ上で動作することを想定して設計されていますが、そのコアな思想は「UIを効率的に構築する」という点にあります。ElectronのレンダラープロセスはChromium上で動作するため、Webブラウザ環境と非常に似ています。したがって、ReactをレンダラープロセスのUI構築に使うことは、Web開発でReactを使うのとほとんど同じ感覚で行えます。
2.3 二つの技術を組み合わせるメリット
ElectronとReactを組み合わせる最大のメリットは以下の通りです。
- Web開発スキルを最大限に活用: HTML, CSS, JavaScript (JSX/TypeScript), Reactの知識があれば、デスクトップアプリを開発できます。新しい言語やUIフレームワークを学ぶ必要がありません。
- 開発効率の向上: Reactのコンポーネント指向や豊富なUIライブラリ、WebpackやBabelといったモダンなフロントエンド開発ツールチェーンを利用できるため、効率的にUIを構築できます。
- クロスプラットフォーム対応: 一つのコードベースでWindows, macOS, Linux向けにアプリケーションをビルドできます。
- リッチなUIとネイティブ機能の融合: Chromiumによる表現力豊かなUIと、Node.jsによるOSネイティブ機能へのアクセスを両立できます。
- 活発なコミュニティ: ElectronもReactも非常に人気があり、広範なドキュメント、チュートリアル、コミュニティサポートがあります。
デメリットとしては、ChromiumとNode.jsを同梱するため、アプリケーションのファイルサイズが比較的大きくなることや、Webブラウザよりもメモリ使用量が多くなる傾向があることが挙げられます。また、OSネイティブのUI要素に完全に一致させるのが難しい場合もあります。しかし、多くのアプリケーションにとって、これらのデメリットはWeb技術による開発のメリットを上回ることが多いです。
3. 開発環境の準備:必要なツールを揃えよう
Electron + React開発を始めるためには、いくつかのツールをインストールし、プロジェクトの初期設定を行う必要があります。
3.1 Node.jsとnpm/yarnのインストール
ElectronはNode.js上で動作し、React開発にはnpm (Node Package Manager) または Yarn といったパッケージマネージャーが必要です。まずはNode.jsをインストールしましょう。
Node.jsの公式サイト (https://nodejs.org/) から、推奨版(LTS版)をダウンロードしてインストールしてください。インストール時にnpmも一緒にインストールされます。
インストールが完了したら、ターミナルまたはコマンドプロンプトを開いて、正しくインストールされたか確認します。
bash
node -v
npm -v
それぞれのバージョン情報が表示されれば成功です。Yarnを使いたい場合は、npmを使ってインストールできます。
bash
npm install -g yarn
yarn -v
この記事では主にnpmコマンドを使って説明しますが、Yarnでも同様に進められます。
3.2 プロジェクトの作成と初期設定
開発用の新しいフォルダを作成し、ターミナルでそのフォルダに移動します。
bash
mkdir my-electron-react-app
cd my-electron-react-app
次に、npmを使ってプロジェクトの初期設定を行います。これによりpackage.json
ファイルが作成されます。
bash
npm init -y
-y
オプションをつけることで、対話形式の質問をスキップし、デフォルトの設定でpackage.json
を生成します。必要に応じて、後でpackage.json
の中身を編集できます。
3.3 必要なパッケージのインストール(コア、ビルドツール、React)
Electron + React開発では、さまざまなnpmパッケージを使用します。これらをプロジェクトにインストールします。開発中にのみ必要なパッケージはdevDependencies
としてインストールするのが一般的です。
“`bash
npm install –save-dev electron electron-builder webpack webpack-cli babel-loader @babel/core @babel/preset-react css-loader style-loader html-webpack-plugin cross-env concurrently wait-on
npm install –save react react-dom
“`
それぞれのパッケージの役割は以下の通りです。
electron
: Electron本体です。electron-builder
: アプリケーションをビルドし、インストーラーなどを作成するためのツールです。webpack
: JavaScriptモジュールをまとめて一つのファイルにバンドルするモジュールバンドラーです。Reactコード(JSXなど)やCSS、画像をElectronが実行できる形に変換・集約するために使用します。webpack-cli
: Webpackをコマンドラインから実行するためのツールです。babel-loader
,@babel/core
,@babel/preset-react
: 新しいJavaScriptの記法(ES6+)やJSXを、Electronが理解できる古いJavaScriptの記法に変換する(トランスパイルする)ためのBabelとそのローダーです。@babel/preset-react
はReact/JSXの変換に特化したプリセットです。css-loader
,style-loader
: CSSファイルをWebpackで処理し、JavaScriptモジュールとして読み込めるようにするためのローダーです。html-webpack-plugin
: Webpackを使って、バンドルされたJavaScriptファイルを自動的に読み込むHTMLファイルを生成するプラグインです。cross-env
: OSによらず環境変数を設定するためのツールです。開発モードと本番モードの切り替えなどに使います。concurrently
: 複数のnpmスクリプトを同時に実行するためのツールです。ElectronとReactの開発サーバーを同時に起動する際に便利です。wait-on
: 特定のURLやポートが使用可能になるまで待機するためのツールです。React開発サーバーが起動するのを待ってからElectronを起動するために使用します。react
: React本体です。react-dom
: ReactをDOM環境(WebブラウザやElectronのレンダラープロセス)で使うためのパッケージです。
これらのパッケージをインストールすると、package.json
のdevDependencies
とdependencies
セクションに追加され、node_modules
フォルダに本体がダウンロードされます。
3.4 create-react-app
を使う方法(補足)
Reactプロジェクトを始める一般的な方法としてcreate-react-app
がありますが、Electronとの組み合わせには少し注意が必要です。create-react-app
は主にシングルページWebアプリケーション(SPA)向けに最適化されており、Webpackなどの設定が隠蔽されています。Electronのレンダラープロセスとして使用することは可能ですが、Electron特有の設定(例えば、Node.js APIへのアクセス制御やプリロードスクリプトの指定など)を行うために、後から設定を公開(eject)したり、カスタム設定を上書きしたりする必要が出てくる場合があります。
初心者にとっては、まずこの記事のように手動で基本的な設定を理解することをお勧めします。手動で設定することで、ElectronとReactがどのように連携しているのか、Webpackがどのような役割を果たしているのかを深く理解できます。
今回は手動でのセットアップを進めていきます。
4. プロジェクトの基本構造を理解する
ここからは、実際にElectronとReactのコードを配置するためのディレクトリ構造と、開発に必要な設定ファイルについて見ていきます。
4.1 推奨ディレクトリ構成
プロジェクトを整理するために、以下のようなディレクトリ構成を推奨します。
my-electron-react-app/
├── package.json # プロジェクトの設定ファイル
├── webpack.config.js # Webpackの設定ファイル
├── main.js # Electronのメインプロセスエントリポイント
├── preload.js # Electronのプリロードスクリプト (後述)
└── src/ # ソースコードディレクトリ
├── index.html # レンダラープロセスが読み込むHTMLファイル
└── renderer/ # Reactアプリケーションのソースコード
├── index.js # Reactアプリケーションのエントリポイント
├── App.js # メインのReactコンポーネント
└── components/ # その他のReactコンポーネントなど
└── ...
main.js
: Electronアプリケーションの起動時に最初に実行されるファイルです。ここでウィンドウを作成したり、アプリケーション全体のイベントを処理したりします。preload.js
: レンダラープロセスがページを読み込む前に実行されるスクリプトです。後述するIPC通信で重要になります。src/index.html
: 各ウィンドウ(レンダラープロセス)で表示されるHTMLファイルです。ここにReactアプリケーションがマウントされます。src/renderer/
: ReactコンポーネントやCSSファイルなど、レンダラープロセス側のコードを置く場所です。webpack.config.js
: Webpackがsrc/renderer/
以下のReactコードなどをビルドするための設定ファイルです。
4.2 package.json
の設定:プロジェクトの心臓部
package.json
はプロジェクトの設定や依存関係、実行可能なスクリプトなどを定義するファイルです。初期設定で作成されたpackage.json
を編集します。
特に重要なのはmain
フィールドとscripts
フィールドです。
json
{
"name": "my-electron-react-app",
"version": "1.0.0",
"description": "My first Electron + React application",
"main": "main.js", # Electronのメインプロセスエントリポイントを指定
"scripts": {
"start": "cross-env NODE_ENV=development webpack serve --config webpack.config.js --mode development",
"electron": "wait-on http://localhost:8080 && cross-env NODE_ENV=development electron .",
"dev": "concurrently \"npm start\" \"npm run electron\"",
"build-react": "cross-env NODE_ENV=production webpack --config webpack.config.js --mode production",
"build-electron": "electron-builder",
"build": "npm run build-react && npm run build-electron",
"package": "npm run build" # 古いバージョンとの互換性のためのエイリアス
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"electron": "^29.1.6",
"electron-builder": "^24.13.3",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"babel-loader": "^9.1.3",
"@babel/core": "^7.24.4",
"@babel/preset-react": "^7.24.1",
"css-loader": "^7.1.1",
"style-loader": "^4.0.0",
"html-webpack-plugin": "^5.6.0",
"cross-env": "^7.0.3",
"concurrently": "^8.2.2",
"wait-on": "^7.2.0"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"build": { # electron-builder の設定はこのフィールドに記述
"appId": "com.yourcompany.yourappname", # アプリケーションのユニークなID
"productName": "MyElectronReactApp", # アプリケーション名 (インストーラーなどに表示される)
"files": [ # ビルドに含めるファイル
"main.js",
"preload.js", # プリロードスクリプトも含める
"package.json",
"build/**/*" # Webpackでビルドしたファイル
],
"directories": {
"output": "dist", # ビルド出力先ディレクトリ
"buildResources": "resources" # アイコンなどのリソースフォルダ (任意)
},
"mac": {
"category": "public.app-category.utilities" # macOSでのカテゴリ
},
"win": {
"target": "nsis" # Windowsでのインストーラー形式 (nsis, portable, etc.)
},
"linux": {
"target": "AppImage" # Linuxでのパッケージ形式 (AppImage, deb, rpm, etc.)
}
}
}
scripts
の説明:
npm start
:cross-env NODE_ENV=development
で開発環境を示す環境変数を設定し、webpack serve
コマンドでWebpackの開発サーバーを起動します。このサーバーはReactアプリケーションをメモリ上でビルドし、ホットリロード機能を提供します。npm run electron
:wait-on http://localhost:8080
でWebpack開発サーバーが起動するのを待ってから、cross-env NODE_ENV=development electron .
コマンドでElectronを実行します。electron .
はカレントディレクトリのpackage.json
のmain
フィールドを見てmain.js
を実行します。npm run dev
:concurrently
を使ってnpm start
とnpm run electron
を同時に実行します。これにより、開発サーバーとElectronアプリが同時に起動し、コード変更が即座に反映されるようになります。npm run build-react
:cross-env NODE_ENV=production
で本番環境向けにWebpackを使ってReactコードをビルドします。ビルド結果は後述するWebpack設定で指定したディレクトリ(通常はbuild
やdist
)に出力されます。npm run build-electron
:electron-builder
コマンドを実行し、Electronアプリケーションをパッケージング・ビルドします。ビルド対象のファイルや出力先はpackage.json
のbuild
フィールドで指定します。npm run build
:npm run build-react
の後にnpm run build-electron
を実行し、ReactコードのビルドからElectronアプリのビルドまでを一括で行います。
build
フィールドはelectron-builder
が参照する設定です。appId
やproductName
、ビルドに含めるファイルなどを指定します。
4.3 Webpackの設定:コードを変換しまとめる (webpack.config.js
)
Webpackは、ReactのJSXや新しいJavaScriptの構文、CSSなどをElectron(Chromium)が理解できる形に変換し、依存関係を解決して一つまたは複数のJavaScriptファイルにまとめる役割を担います。また、開発サーバー機能も提供します。
プロジェクトのルートディレクトリにwebpack.config.js
ファイルを作成し、以下のように記述します。
“`javascript
const path = require(‘path’);
const HtmlWebpackPlugin = require(‘html-webpack-plugin’);
const webpack = require(‘webpack’); // Webpack itself
module.exports = (env, argv) => {
const isDevelopment = argv.mode === ‘development’;
return {
// Electronのレンダラープロセス向けにバンドル
target: ‘electron-renderer’,
// エントリポイント: Reactアプリケーションの開始ファイル
entry: './src/renderer/index.js',
// 出力設定: バンドルされたファイルの出力場所
output: {
path: path.resolve(__dirname, 'build'), // 出力ディレクトリ
filename: 'static/js/[name].[contenthash:8].js', // 出力ファイル名
publicPath: isDevelopment ? '/' : './' // 公開パス (開発サーバー or ファイルシステム)
},
// モジュールの解決設定
resolve: {
extensions: ['.js', '.jsx', '.json'], // .js, .jsx, .json ファイルをインポート時に拡張子省略可能にする
},
module: {
rules: [
{
// JavaScript/JSX ファイルを Babel で処理
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-react', // React (JSX) を変換
// 必要に応じて、ターゲット環境に応じたプリセットを追加 (例: @babel/preset-env)
],
},
},
},
{
// CSS ファイルを処理 (style-loader はスタイルを <style> タグとして挿入)
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
// 画像ファイルを処理 (file-loader または asset/resource)
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
generator: {
filename: 'static/media/[name].[hash:8][ext]'
}
},
{
// フォントファイルを処理
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'static/fonts/[name].[hash:8][ext]'
}
},
],
},
// プラグインの設定
plugins: [
// HTMLファイルを生成し、バンドルされたスクリプトを自動的に挿入
new HtmlWebpackPlugin({
template: './src/index.html', // 元となるHTMLテンプレート
filename: 'index.html', // 出力されるHTMLファイル名
}),
// 環境変数をReactコード内で利用可能にする
new webpack.DefinePlugin({
// JSON.stringifyが必要なことに注意
'process.env.NODE_ENV': JSON.stringify(argv.mode),
'process.env.PUBLIC_URL': JSON.stringify(isDevelopment ? '/' : './')
})
],
// 開発サーバーの設定 (開発モードのみ)
devServer: {
static: {
directory: path.join(__dirname, 'build'), // 静的ファイルの提供元
},
compress: true, // gzip圧縮を有効
port: 8080, // 開発サーバーのポート番号
historyApiFallback: true, // HTML5 History API を使う場合に必要 (React Routerなど)
hot: true, // ホットモジュールリプレースメント (HMR) を有効
// devMiddleware: {
// publicPath: '/',
// },
},
// 開発ツール設定 (ソースマップなど)
devtool: isDevelopment ? 'eval-source-map' : 'source-map',
// パフォーマンスヒントの無効化 (Electronアプリではファイルサイズが大きくなるため)
performance: {
hints: false,
},
// Electron特有の設定 (Node.js APIへのアクセスなど)
// target: 'electron-renderer' を設定済み
// electron-rendererターゲットでは、デフォルトでNode.jsモジュールは無視されないが、
// レンダラープロセスでのNode.js APIアクセスは BrowserWIndow の webPreferences で制御する
};
};
“`
この設定ファイルは、Webpackの基本的な機能を構成しています。特に重要なのは以下の点です。
target: 'electron-renderer'
: Electronのレンダラープロセス向けにコードをバンドルすることを指定します。これにより、Node.jsの組み込みモジュールなどが正しく扱われるようになります。entry
: ReactアプリケーションのメインとなるJavaScriptファイル(通常はindex.js
など)を指定します。output
: バンドルされたJavaScriptファイルやその他のアセットがどこに出力されるかを指定します。ここではbuild
フォルダにstatic/js/
配下に出力するように設定しています。module.rules
: ファイルの種類に応じて、どのローダーを使って処理するかを指定します。ここではJavaScript/JSXファイルをBabelで、CSSファイルをcss-loader
とstyle-loader
で処理するように設定しています。plugins
: Webpackの追加機能を有効にします。HtmlWebpackPlugin
はsrc/index.html
をテンプレートとして、ビルドされたJavaScriptファイルを自動的に<script>
タグとして追加した新しいindex.html
をbuild
フォルダに出力します。DefinePlugin
は環境変数をReactコード内で参照できるようにします。devServer
:npm start
コマンドで起動する開発サーバーの設定です。ポート番号やホットリロードの設定などを行います。devtool
: デバッグ用のソースマップ生成設定です。開発中はeval-source-map
、本番ビルドではsource-map
が推奨されます。
これで、ElectronとReactを連携させるための基本的な設定ファイル群が揃いました。
5. Electronアプリケーションの核:メインプロセス (main.js
)
Electronアプリケーションは、メインプロセスから始まります。メインプロセスの役割は、アプリケーションの起動、ウィンドウの作成と管理、OSネイティブ機能との連携などです。
プロジェクトのルートディレクトリにmain.js
ファイルを作成し、以下の基本的なコードを記述します。
“`javascript
const { app, BrowserWindow, ipcMain } = require(‘electron’);
const path = require(‘path’);
const isDev = require(‘electron-is-dev’); // 開発モードか判定するためのモジュール (必要に応じてインストール: npm install electron-is-dev)
let mainWindow; // ウィンドウオブジェクトをグローバル参照に保持
function createWindow() {
// 新しいブラウザウィンドウを作成します。
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// プリロードスクリプトを指定 (後述)
// レンダラープロセスがNode.js APIに直接アクセスするのを防ぎつつ、
// 安全なAPIを公開するために推奨される設定
preload: path.join(__dirname, ‘preload.js’),
nodeIntegration: false, // レンダラープロセスでのNode.js統合を無効化
contextIsolation: true, // コンテキスト分離を有効化 (推奨されるセキュリティ設定)
enableRemoteModule: false // remoteモジュールを無効化 (非推奨になったため)
},
});
// 開発モードと本番モードで読み込むコンテンツを切り替え
// 開発モード: Webpack開発サーバーから読み込む
// 本番モード: ビルドされたHTMLファイルを読み込む
const startUrl = isDev
? ‘http://localhost:8080’ // Webpack開発サーバーのURL
: file://${path.join(__dirname, 'build', 'index.html')}
; // ビルドされたHTMLファイル
mainWindow.loadURL(startUrl);
// 開発ツールを開く (開発モードのみ)
if (isDev) {
mainWindow.webContents.openDevTools();
}
// ウィンドウが閉じられたときの処理
mainWindow.on(‘closed’, function () {
// ウィンドウオブジェクトへの参照を解除します。
mainWindow = null;
});
}
// Electronが初期化を完了し、ブラウザウィンドウを作成する準備ができたときに呼ばれます。
app.whenReady().then(() => {
createWindow();
// macOSでDockアイコンをクリックされたときにウィンドウを再作成する処理
app.on(‘activate’, function () {
if (mainWindow === null) {
createWindow();
}
});
});
// 全てのウィンドウが閉じられたときにアプリケーションを終了します。
app.on(‘window-all-closed’, function () {
// macOSでは、ユーザーがCmd + Qで明示的に終了するまで、アプリケーションとそのメニューバーはアクティブなままになるのが一般的です。
if (process.platform !== ‘darwin’) {
app.quit();
}
});
// アプリケーションがフォーカスを失ったときに最小化するなど、他のイベントリスナーもここに追加できます。
// IPC通信の例(ここでは受信側のみ)
ipcMain.on(‘set-title’, (event, title) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
win.setTitle(title);
});
// プリロードスクリプトからのIPC通信を受ける例
ipcMain.on(‘send-message-to-main’, (event, message) => {
console.log(‘Message from renderer (via preload):’, message);
// レンダラープロセスにメッセージを返す例
if (mainWindow) {
mainWindow.webContents.send(‘message-from-main’, ‘Received your message: “‘ + message + ‘”‘);
}
});
“`
このコードの重要な部分を解説します。
const { app, BrowserWindow, ipcMain } = require('electron');
: Electronモジュールから必要なオブジェクト(アプリケーション全体を制御するapp
、ウィンドウを作成するBrowserWindow
、メインプロセス側のIPCモジュールipcMain
)をインポートしています。let mainWindow;
: 作成したウィンドウオブジェクトを保持するための変数です。ウィンドウが閉じられたときに参照を解除するため、グローバルまたはスコープ外からアクセス可能な場所で宣言します。createWindow()
関数:BrowserWindow
クラスのインスタンスを作成し、ウィンドウのサイズや、webPreferences
を設定します。webPreferences
: Electron開発において最も重要な設定の一つです。レンダラープロセス(Reactコードが動く環境)の挙動を制御します。preload
: レンダラープロセスがスクリプトを実行する前に読み込まれるプリロードスクリプトのパスを指定します。これは、Node.js APIへの安全なアクセスを提供したり、IPC通信を設定したりするために使われます。(後述のpreload.js
で詳しく説明します)nodeIntegration: false
: 非常に重要です! これをfalse
に設定すると、レンダラープロセスで直接require
やNode.jsの組み込みモジュール(fs
,path
など)を使うことができなくなります。セキュリティ上の理由から、デフォルトでfalse
に設定し、必要な機能はプリロードスクリプト経由で安全に公開するのが推奨されます。contextIsolation: true
: これも非常に重要です! これをtrue
に設定すると、プリロードスクリプトとレンダラープロセスのJavaScriptコンテキストが分離されます。これにより、悪意のあるスクリプトがプリロードスクリプトで公開されたAPIにアクセスしたり、ElectronやNode.jsのグローバルオブジェクトを改変したりするのを防ぎます。デフォルトでtrue
に設定することを強く推奨します。enableRemoteModule: false
:remote
モジュールは非推奨になり、セキュリティリスクがあるため無効化します。代わりにIPC通信やcontextBridge
を使用します。
startUrl
: 開発モード (isDev
がtrue) のときは、Webpack開発サーバーのURL (http://localhost:8080
) を読み込みます。これにより、コードを変更したときにホットリロードが効くようになります。本番モードのときは、webpack
でビルドされたindex.html
ファイルをローカルファイルとして読み込みます。mainWindow.loadURL(startUrl);
: 作成したウィンドウに指定したURLまたはファイルを読み込ませます。mainWindow.webContents.openDevTools();
: 開発者ツールを開きます。Web開発で慣れ親しんだChrome DevToolsと同じものが使えます。Element、Console、Network、Applicationなどのタブを使って、レンダラープロセスのデバッグができます。app.whenReady().then(...)
: Electronアプリケーションの初期化が完了した後に実行される処理を定義します。ここでcreateWindow()
を呼び出して最初のウィンドウを作成します。app.on('window-all-closed', ...)
: 開いているすべてのウィンドウが閉じられたときに呼ばれるイベントハンドラです。通常、ここでアプリケーションを終了させます。macOSではドックにアイコンが残るため、プラットフォームを判定して終了処理を分けるのが一般的です。app.on('activate', ...)
: macOSで、アプリケーションがアクティブ化された(ドックのアイコンをクリックされたなど)ときに呼ばれるイベントハンドラです。ウィンドウが一つも開いていない場合に新しいウィンドウを作成する処理を記述します。ipcMain.on(...)
: レンダラープロセスから送られてきたIPCメッセージを受信するリスナーを設定します。この例では、set-title
とsend-message-to-main
というチャンネルでメッセージを受け取っています。
このmain.js
ファイルは、Electronアプリケーションの全体を制御する司令塔のような役割を果たします。
6. Reactアプリケーションの実装:レンダラープロセス
Electronのレンダラープロセスは、Chromium上で動作するWebページのようなものです。ここにReactを使ってUIを構築します。
6.1 Reactアプリケーションのエントリポイント (src/renderer/index.js
)
Reactアプリケーションの開始地点となるファイルです。通常、ここでReactのルートコンポーネントをDOMにマウントします。
src/renderer/index.js
ファイルを作成し、以下のように記述します。
“`javascript
import React from ‘react’;
import ReactDOM from ‘react-dom/client’; // React 18 の場合
import App from ‘./App’; // 作成するReactコンポーネントをインポート
import ‘./index.css’; // 必要に応じてグローバルCSSをインポート
// React 18 の場合
const container = document.getElementById(‘root’);
const root = ReactDOM.createRoot(container);
root.render(
);
// React 17 以前の場合
// import ReactDOM from ‘react-dom’;
// ReactDOM.render(
//
//
//
// document.getElementById(‘root’)
// );
“`
このコードは、HTMLファイル(後述するsrc/index.html
)内にIDがroot
の要素があることを想定しています。ReactDOM.createRoot()
(React 18)またはReactDOM.render()
(React 17以前)を使って、<App />
コンポーネントをその要素にレンダリングしています。<React.StrictMode>
は開発中の潜在的な問題を検出するためのラッパーです。
6.2 最初のReactコンポーネントを作成する (src/renderer/App.js
)
Reactアプリケーションのメインコンポーネントを作成します。ここでは、簡単なテキストを表示し、Electronの機能と連携するボタンを追加してみましょう。
src/renderer/App.js
ファイルを作成し、以下のように記述します。
“`javascript
import React, { useState, useEffect } from ‘react’;
function App() {
const [message, setMessage] = useState(‘Waiting for message from main process…’);
const [appTitle, setAppTitle] = useState(‘My Electron App’);
// コンポーネントがマウントされたときに一度だけ実行される処理
useEffect(() => {
// プリロードスクリプトで公開されたAPIが存在するか確認
if (window.electronAPI) {
// メインプロセスからのメッセージを受信するリスナーを設定
const removeListener = window.electronAPI.onMessageFromMain((event, msg) => {
setMessage(msg);
});
// コンポーネントがアンマウントされるときにリスナーを解除
return () => removeListener();
} else {
setMessage('Electron API not available in this environment.');
}
}, []); // 空の配列を渡すことで、マウント時とアンマウント時にのみ実行される
// ボタンクリック時にメインプロセスにメッセージを送る関数
const sendMessageToMain = () => {
if (window.electronAPI) {
window.electronAPI.sendMessageToMain(‘Hello from renderer!’);
} else {
console.error(‘Electron API not available.’);
}
};
// ボタンクリック時にウィンドウタイトルを変更する関数
const changeTitle = () => {
if (window.electronAPI) {
// 例: 入力フィールドの値を使う
// const newTitle = document.getElementById(‘titleInput’).value;
const newTitle = New Title ${Math.random().toFixed(2)}
; // 例としてランダムなタイトル
setAppTitle(newTitle);
window.electronAPI.setTitle(newTitle);
} else {
console.error(‘Electron API not available.’);
}
};
return (
Electron + React アプリ
現在のウィンドウタイトル: {appTitle}
メインプロセスからのメッセージ: {message}
<button onClick={sendMessageToMain} style={{ marginRight: '10px' }}>
メインプロセスにメッセージ送信
</button>
<button onClick={changeTitle}>
ウィンドウタイトル変更
</button>
{/* 必要に応じて入力フィールドなどを追加 */}
{/* <br/>
<input type="text" id="titleInput" placeholder="新しいタイトルを入力"/> */}
</div>
);
}
export default App;
“`
このReactコンポーネントは、useState
フックを使って画面に表示するメッセージとタイトルを管理しています。useEffect
フックを使って、コンポーネントがマウントされたときに一度だけ、メインプロセスからのメッセージを受信するリスナーを設定しています。
ここで注目すべき点は、window.electronAPI
というオブジェクトを使っていることです。これは、直接Node.jsのipcRenderer
などを使う代わりに、後述するプリロードスクリプトで安全に公開されたカスタムAPIを呼び出している部分です。これにより、レンダラープロセスにNode.js APIへのフルアクセスを許可することなく、必要な機能だけを安全に利用できます。
ボタンのクリックハンドラでは、同様にwindow.electronAPI
経由でメインプロセスにメッセージを送信したり、ウィンドウタイトルを変更するためのメソッドを呼び出しています。
6.3 HTMLファイル (src/index.html
) の役割
このHTMLファイルは、レンダラープロセスがロードするページの基本構造を定義します。Reactアプリケーションは、このHTMLファイル内の特定の要素にマウントされます。
src/index.html
ファイルを作成し、以下のように記述します。
“`html
“`
このHTMLファイルは非常にシンプルです。
<div id="root"></div>
: これが、src/renderer/index.js
で指定した、Reactアプリケーションがマウントされる要素です。- Webpackの
html-webpack-plugin
が、ビルドされたJavaScriptやCSSファイルへの参照をこのHTMLファイルに自動的に挿入してくれます。そのため、<script>
タグや<link rel="stylesheet">
タグを自分で書く必要はありません。
7. ElectronとReactの連携:IPC通信の基礎
前述の通り、Electronのメインプロセスとレンダラープロセスは独立しており、直接互いの変数や関数にアクセスすることはできません。両プロセス間でデータやコマンドをやり取りするためには、IPC (Inter-Process Communication) を使用します。
7.1 なぜIPCが必要なのか?(メインとレンダラーの分離)
- メインプロセス: Node.js環境で実行され、ファイルシステムアクセスやOS機能など、システムレベルの操作が可能です。
- レンダラープロセス: Chromium環境で実行され、UIの描画を担当します。セキュリティ上の理由から、デフォルトではNode.js APIへのアクセスが制限されています。
例えば、レンダラープロセスで「ファイルを保存」ボタンが押されたとします。ファイルを保存する操作はNode.jsのfs
モジュールを使う必要があり、これはメインプロセスでのみ安全に実行できます。したがって、レンダラープロセスはこの要求をメインプロセスに伝え、メインプロセスが実際のファイル保存処理を行う必要があります。この「伝える」仕組みがIPCです。
7.2 ipcMain
と ipcRenderer
の使い方
ElectronはIPCのためにipcMain
モジュール(メインプロセス用)とipcRenderer
モジュール(レンダラープロセス用)を提供しています。
ipcMain
: メインプロセスで使います。ipcMain.on(channel, listener)
: 特定のチャンネルでレンダラープロセスからメッセージを受信するリスナーを設定します。BrowserWindow.webContents.send(channel, ...args)
: 特定のウィンドウのレンダラープロセスにメッセージを送信します。
ipcRenderer
: レンダラープロセスで使います。ipcRenderer.send(channel, ...args)
: メインプロセスにメッセージを送信します。ipcRenderer.on(channel, listener)
: 特定のチャンネルでメインプロセスからメッセージを受信するリスナーを設定します。ipcRenderer.invoke(channel, ...args)
: メインプロセスで非同期処理を実行し、その結果を待ち受けるための新しいAPIです(Electron 9以降推奨)。ipcRenderer.handle(channel, listener)
:ipcMain
側でinvoke
で送られてきたメッセージを処理するためのリスナーです(Electron 9以降推奨)。
以前は同期通信のためのipcRenderer.sendSync
とipcMain.on
の組み合わせもありましたが、UIをブロックする可能性があるため非推奨です。非同期通信(send
/on
またはinvoke
/handle
)を使うことが推奨されます。
7.3 簡単なIPC通信の実装例(メイン←→レンダラー)
前述のmain.js
とsrc/renderer/App.js
のコードで、既に簡単なIPC通信の仕組みを導入しています。しかし、そのままではセキュリティ上の問題があります。
7.4 プリロードスクリプト (preload.js
) の重要性とセキュリティ
以前のElectronのバージョンでは、webPreferences
でnodeIntegration: true
を設定し、レンダラープロセスから直接require('electron')
やrequire('fs')
などを使ってNode.js APIにアクセスすることが可能でした。しかし、これは非常に危険です。
もしレンダラープロセスで外部の、信頼できないコンテンツ(例えば、インターネット上のWebページやユーザーが入力したHTML)を読み込んだ場合、そのコンテンツ内のJavaScriptコードがrequire('electron').remote.require('child_process').exec()
のようなコードを実行できてしまい、ユーザーのコンピューター上で任意のコマンドを実行される可能性があります。これはクロスサイトスクリプティング(XSS)攻撃のデスクトップ版のようなものです。
このため、ElectronではnodeIntegration: false
とcontextIsolation: true
をデフォルトとし、プリロードスクリプトを介して必要なAPIだけを安全に公開することを強く推奨しています。
プリロードスクリプトは、レンダラープロセスがページを読み込む前に実行されるスクリプトです。このスクリプトは特別なコンテキスト(Node.js APIとElectron APIにアクセスできるが、レンダラープロセスのグローバルオブジェクトには直接アクセスできない)で実行されます。
7.5 contextBridge
を使った安全なAPI公開 (preload.js
)
プリロードスクリプトでcontextBridge
モジュールを使うと、隔離されたコンテキスト(プリロードスクリプト側)から、レンダラープロセスのグローバルオブジェクト(window
)に特定のAPIを安全に公開できます。
プロジェクトのルートディレクトリにpreload.js
ファイルを作成し、以下のように記述します。
“`javascript
const { contextBridge, ipcRenderer } = require(‘electron’);
// レンダラープロセスに公開したいAPIを定義
const api = {
// メインプロセスへのメッセージ送信機能
sendMessageToMain: (message) => ipcRenderer.send(‘send-message-to-main’, message),
// メインプロセスからのメッセージ受信機能
// コールバック関数を引数に取り、ipcRenderer.on のリスナー内でコールバックを実行
// イベントオブジェクトはセキュリティのため第一引数から除外することが推奨されます
onMessageFromMain: (callback) => ipcRenderer.on(‘message-from-main’, (event, …args) => callback(…args)),
// ウィンドウタイトルを設定する機能
setTitle: (title) => ipcRenderer.send(‘set-title’, title),
// その他のElectron/Node.js関連機能…
// 例: ファイル選択ダイアログを開く(invoke/handle を使う例)
// openFileDialog: () => ipcRenderer.invoke(‘open-file-dialog’),
// readTextFile: (filePath) => ipcRenderer.invoke(‘read-text-file’, filePath),
// レンダラープロセスで安全に実行できる他のスクリプトやユーティリティ関数など
// someUtilityFunction: (arg) => { / … / }
};
// ‘electronAPI’ という名前で上記のAPIオブジェクトをレンダラープロセスの window オブジェクトに公開
contextBridge.exposeInMainWorld(‘electronAPI’, api);
console.log(‘Preload script loaded.’);
“`
このプリロードスクリプトは以下のことを行います。
contextBridge
とipcRenderer
モジュールをインポートします。- レンダラープロセスに公開したいカスタムAPIを定義した
api
オブジェクトを作成します。 ipcRenderer.send()
やipcRenderer.on()
を使って、メインプロセスとのIPC通信を行います。contextBridge.exposeInMainWorld('electronAPI', api)
を使って、このapi
オブジェクトをレンダラープロセスのwindow.electronAPI
として利用可能にします。'electronAPI'
という名前は任意です。
このようにすることで、レンダラープロセスはプリロードスクリプトで明示的に公開されたwindow.electronAPI
オブジェクト経由でのみ、メインプロセスとのIPC通信を行ったり、特定のElectron機能を利用したりできるようになります。これにより、レンダラープロセスが持つNode.js APIへのアクセス権限を最小限に抑え、セキュリティリスクを大幅に低減できます。
main.js
でwebPreferences
にpreload: path.join(__dirname, 'preload.js')
を指定したことを思い出してください。これで、このpreload.js
がウィンドウ作成時に実行されるようになります。
これで、ElectronとReact間の安全なIPC通信の仕組みが整いました。
8. React開発の進め方:状態管理、ルーティング、UIライブラリ
レンダラープロセス側の開発は、基本的に通常のReact Webアプリケーション開発と非常によく似ています。
8.1 Reactの基本的な機能のおさらい
- コンポーネント: UIの部品を作成し、組み合わせて複雑なUIを構築します。関数コンポーネントとクラスコンポーネントがありますが、最近はフックを使える関数コンポーネントが主流です。
- JSX: JavaScriptの中にHTMLのようなタグを書ける構文拡張です。
- Props: 親コンポーネントから子コンポーネントにデータを渡すための仕組みです。
- State: コンポーネント自身が持つ状態(データ)です。
useState
フックを使って管理します。状態が変更されると、コンポーネントは再レンダリングされます。 - Hooks:
useState
,useEffect
,useContext
など、関数コンポーネントでReactの強力な機能(状態、ライフサイクル、コンテキストなど)を使えるようにする機能です。 - 状態管理: アプリケーション全体で共有する複雑な状態を管理するためには、Context API、またはRedux、Zustand、Recoilなどのライブラリが使われます。デスクトップアプリの場合も同様です。
- ルーティング: シングルページアプリケーションのように、URLパスに応じて表示するコンポーネントを切り替えるには、
react-router-dom
などのルーティングライブラリを使用します。
React開発に関する詳細は、多くの優れたチュートリアルやドキュメントが公開されていますので、そちらを参照してください。
8.2 Electron固有の機能との連携(ダイアログ、メニューなど)
Electronアプリケーションでは、OSネイティブのダイアログ(ファイル選択、メッセージ表示など)、メニューバー、通知などの機能を利用できます。これらの機能はNode.js APIを必要とするため、通常はメインプロセスで実行する必要があります。
レンダラープロセス(Reactコンポーネント)からこれらの機能を使いたい場合は、IPC通信を使ってメインプロセスに依頼します。
例:ファイル選択ダイアログを開く
-
メインプロセス (
main.js
または別のメインプロセス側のモジュール):ipcMain.handle('open-file-dialog', async () => { ... })
のようなリスナーを設定します。- リスナー関数内で
dialog.showOpenDialog(mainWindow, options)
を呼び出し、ファイル選択ダイアログを表示します。 - ユーザーが選択したファイルのパスを非同期処理の結果として返します。
-
プリロードスクリプト (
preload.js
):contextBridge.exposeInMainWorld('electronAPI', { ..., openFileDialog: () => ipcRenderer.invoke('open-file-dialog') })
のように、dialog.showOpenDialog
をラップした関数をレンダラープロセスに公開します。
-
レンダラープロセス (Reactコンポーネント):
- ボタンのクリックハンドラなどで、
await window.electronAPI.openFileDialog()
を呼び出し、選択されたファイルのパスを受け取ります。
- ボタンのクリックハンドラなどで、
このように、Electron固有のネイティブ機能は、IPCとプリロードスクリプトを介してReactコードから利用するのが安全かつ標準的な方法です。
9. 開発効率を高める:デバッグとホットリロード
効果的な開発のためには、コードの変更がすぐに反映され、問題が発生したときに原因を特定しやすい環境が重要です。
9.1 開発者ツールの活用(Electron DevTools, React DevTools)
- Electron DevTools:
main.js
でmainWindow.webContents.openDevTools()
を呼び出すことで、WebブラウザのChrome DevToolsと同じものが使えます。これはレンダラープロセス(Reactコードが動いている環境)のデバッグに非常に役立ちます。DOM構造、CSSスタイル、JavaScriptの実行状況(コンソール、デバッガー)、ネットワーク通信、アプリケーションストレージなどを確認できます。 - React Developer Tools: Reactのコンポーネント階層、props、stateを確認・編集できるChrome拡張機能(Electronでも利用可能)です。これはReactアプリケーションのデバッグに非常に便利です。Chromeウェブストアからインストールしておきましょう。Electronアプリを開いた状態でDevToolsを開くと、Reactタブが表示されます。
9.2 ホットリローディングの設定
Webpack開発サーバー (webpack serve
) は、コードを変更したときにブラウザ(Electronのレンダラープロセス)を自動的にリロードしたり、HMR (Hot Module Replacement) 機能を使ってアプリケーション全体をリロードすることなく変更部分だけを差し替えたりする機能を提供します。
webpack.config.js
のdevServer.hot: true
設定と、適切なローダー・プラグイン(Reactの場合は@pmmmwh/react-refresh-webpack-plugin
なども検討できますが、基本的なWebpack設定でhot: true
だけでも多くの場合機能します)により、開発中の利便性が大幅に向上します。package.json
でconcurrently
とwait-on
を使ってWebpack開発サーバーとElectronを同時に起動するスクリプト(npm run dev
)を設定したのは、このホットリロードを活用するためです。
10. アプリケーションのビルドと配布
開発したElectron + Reactアプリケーションを他のユーザーがインストールして実行できるようにするためには、アプリケーションをビルドし、配布可能な形式にパッケージングする必要があります。
ここでは、前述のelectron-builder
パッケージを使った方法を紹介します。
10.1 Electron Builder の導入
electron-builder
は、ElectronアプリケーションをmacOS (.dmg
, .pkg
), Windows (.exe
, squirrel
), Linux (.AppImage
, .deb
, .rpm
) などの形式でビルド・パッケージングするための多機能なツールです。デジタル署名、自動アップデート対応、Native Addonsの再ビルドなどもサポートしています。
package.json
のbuild
フィールドに設定を記述するか、electron-builder.yml
などの外部設定ファイルを使用します。前述のpackage.json
の例では、基本的なbuild
フィールドの設定を含めています。
10.2 ビルド設定ファイル (package.json
または electron-builder.yml
) の作成
package.json
にbuild
フィールドを追加するのが最も簡単な方法です。設定可能なオプションは多岐にわたりますが、最低限必要なのは以下の情報です。
appId
: アプリケーションを一意に識別するためのIDです(例:com.yourcompany.yourappname
)。productName
: アプリケーションの名前です。インストーラーやOSのアプリケーション一覧に表示されます。files
: ビルド成果物に含まれるべきファイルやディレクトリを指定します。Webpackでビルドしたレンダラープロセスのコード、メインプロセスのコード、package.json
などが含まれる必要があります。"build/**/*"
は、Webpackの出力ディレクトリであるbuild
フォルダの中身全てを含めるという意味です。directories.output
: ビルドされたパッケージが出力されるディレクトリを指定します(例:"dist"
)。- プラットフォーム固有の設定:
mac
,win
,linux
などのフィールドで、各OS向けの固有の設定(アイコン、インストーラー形式、証明書など)を指定できます。
より詳細な設定や高度な機能については、electron-builder
の公式ドキュメントを参照してください。
10.3 ビルドコマンドの実行
package.json
に設定したビルドスクリプトを実行します。
bash
npm run build
このコマンドは、まずnpm run build-react
を実行してReactコードを本番用にビルドし(build
フォルダに出力)、次にnpm run build-electron
を実行してElectron Builderによるパッケージングとビルドを行います。
ビルドが成功すると、package.json
のbuild.directories.output
で指定したディレクトリ(この例ではdist
)内に、各OS向けの配布ファイルが生成されます。
10.4 各OS向けの配布ファイルについて
electron-builder
は、ターゲットOSに応じて様々な形式の配布ファイルを生成できます。
- Windows:
.exe
インストーラー(NSIS形式)、.exe
ポータブル版、AppXパッケージなど。 - macOS:
.dmg
ディスクイメージ、.pkg
インストーラー、zipファイルなど。 - Linux:
.AppImage
(多くのLinuxディストリビューションで実行可能)、.deb
パッケージ(Debian/Ubuntu系)、.rpm
パッケージ(Fedora/CentOS系)など。
生成されたファイルを、アプリケーションを利用するユーザーに提供することで、開発環境を持っていないユーザーでもあなたのアプリケーションを実行できるようになります。
11. さらに進んだ開発に向けて:セキュリティと機能拡張
Electron開発には、Web開発とは異なる考慮事項がいくつかあります。特にセキュリティは非常に重要です。
11.1 セキュリティの考慮事項(Node.js APIの露出、リモートコンテンツ)
最も重要なセキュリティ対策は、セクション7.4で解説したように、レンダラープロセスでNode.js統合を無効化し (nodeIntegration: false
)、コンテキスト分離を有効化 (contextIsolation: true
) し、プリロードスクリプト (preload.js
) とcontextBridge
を介して必要なAPIのみを安全に公開することです。
その他のセキュリティに関するベストプラクティスとして以下が挙げられます。
- 外部コンテンツの読み込みに注意: 信頼できないURLやユーザー生成のHTMLコンテンツを
BrowserWindow
に直接読み込むのは避けてください。もし読み込む必要がある場合は、webview
タグや新しいBrowserWindow
を使い、Node.js統合を完全に無効化するなど、厳重なセキュリティ対策を施してください。 shell.openExternal
の利用: 外部のリンク(<a>
タグなど)をクリックしたときに、Electronウィンドウ内で開くのではなく、ユーザーのデフォルトブラウザで開くようにします。これはshell.openExternal(url)
を使ってメインプロセスで行うのが安全です。- Node.js API の濫用を避ける: メインプロセスでも、ユーザーからの入力や信頼できないデータを使って直接ファイルパスを組み立てたり、シェルコマンドを実行したりするのは危険です。入力値のサニタイズやバリデーションを必ず行い、可能な限りNode.js APIの利用範囲を限定してください。
Electronの公式ドキュメントには「Security, Native Capabilities, and Your Responsibility」というセキュリティに関する重要なセクションがありますので、開発を進める上で必ず一読してください。
11.2 クラッシュレポート、自動アップデート、Native Addons
- クラッシュレポート: アプリケーションがクラッシュした場合に、その情報を収集して開発者に送信する仕組みです。Electronは
crashReporter
モジュールを提供しており、外部サービスと連携して実装できます。 - 自動アップデート: 配布後にアプリケーションを更新する仕組みです。
electron-updater
のようなライブラリを使うと、簡単に実装できます。自動アップデート機能もメインプロセスで実装する必要があります。 - Native Addons: C++などのネイティブコードで記述されたモジュールをNode.jsやElectronから呼び出すための仕組みです。パフォーマンスが要求される処理や、特定のOSネイティブAPIにアクセスしたい場合に利用しますが、ビルドプロセスが複雑になります。
これらの高度な機能は、アプリケーションの要件に応じて検討してみてください。
11.3 テスト
デスクトップアプリケーション開発においても、テストは重要です。
- ユニットテスト/結合テスト: Reactコンポーネントのテストは、通常のWeb開発と同様にJestやReact Testing Libraryなどを使って行えます。メインプロセスやプリロードスクリプトのコードもNode.js環境でテストできます。
- e2eテスト (End-to-End Test): アプリケーション全体を実際に起動して、ユーザー操作をシミュレートしながらテストを行う方法です。Spectron(Electronチームが開発していたがメンテナンスモード)やPlaywrightのようなツールがElectronアプリケーションのe2eテストに使えます。
12. まとめ:デスクトップアプリ開発の旅はまだ始まったばかり
この記事では、ElectronとReactを使ってデスクトップアプリケーション開発を始めるための基本的な知識と具体的な手順を詳細に解説しました。
- ElectronがWeb技術でデスクトップアプリを作る仕組み(メイン/レンダラープロセス、Chromium+Node.js)を理解しました。
- ReactをレンダラープロセスのUI構築に使うメリットを確認しました。
- 開発環境のセットアップ、
package.json
、webpack.config.js
といった初期設定ファイルについて学びました。 main.js
でウィンドウを作成・管理し、アプリケーションのライフサイクルを制御する方法を学びました。src/renderer/
以下にReactアプリケーションを構築する方法と、src/index.html
の役割を理解しました。- メインプロセスとレンダラープロセス間のデータ通信にIPCを使用する方法、そしてプリロードスクリプトと
contextBridge
を使った安全なAPI公開の重要性を学びました。 - 開発効率を高めるデバッグ方法やホットリロードについて触れました。
electron-builder
を使ったアプリケーションのビルドと配布手順を確認しました。- セキュリティに関する重要な考慮事項についても言及しました。
ElectronとReactの組み合わせは、Web開発者にとってデスクトップアプリケーション開発の世界への強力な扉を開いてくれます。この記事で学んだことは、その旅の出発点に過ぎません。
ここからさらに、Reactの状態管理ライブラリを導入したり、Electronの他のモジュール(メニュー、ダイアログ、通知など)を駆使したり、複雑なアプリケーションロジックを実装したりと、開発の可能性は無限に広がります。
このガイドが、あなたのデスクトップアプリケーション開発の第一歩を踏み出す助けになれば幸いです。ぜひ、実際にコードを書いて、様々な機能を試してみてください。開発を進める中で壁にぶつかったときは、公式ドキュメントやコミュニティのリソースを活用しましょう。
あなたの素晴らしいアイデアが、デスクトップアプリケーションとして形になることを応援しています!