Webとデスクトップの架け橋
世界レベルの デスクトップアプリケーションを構築 するために、C++やC#の長年の経験はもう必要ありません。Discord、Slack、VS Codeといったアプリは、Web技術がデスクトップを支配できることを証明しました。Electron.jsにReactとTypeScriptを組み合わせることで、複雑なソフトウェアに必要な 型安全性を維持しながら 、膨大なライブラリのエコシステムを活用できます。
このスタックでいくつかの製品用ツールをリリースしてきましたが、開発スピードにおいて「スイートスポット」であると感じています。 UIとシステムの境界 を尊重すれば、安定性は驚くほど高いです。理論はさておき、本番環境に対応できるセットアップを構築していきましょう。
5分でHello Worldを構築する
手動での設定は、フラストレーションの元です。モダンなワークフローにはelectron-viteをお勧めします。これは驚異的に速いホットモジュールリプレースメント(HMR)を提供し(多くの場合100ms未満でリフレッシュ)、TypeScriptのコンパイルも自動で処理してくれます。
ターミナルを開き、以下を実行します:
npm create @electron-vite/project@latest my-electron-app -- --template react-ts
プロジェクトに移動して、開発環境を起動します:
cd my-electron-app
npm install
npm run dev
すぐにウィンドウが表示されるはずです。フォルダ構成(main、renderer、preload)に注目してください。これは単なる整理整頓ではなく、セキュリティ上の要件です。これらのレイヤーを分離しておくことで、UIの脆弱性が原因で攻撃者にユーザーのコンピュータを完全に制御されるのを防ぐことができます。
内部の仕組み
本格的なものを作るには、Electron의マルチプロセスモデルを理解する必要があります。これは単一のアプリというよりも、プログラムのクラスターのように機能します。
1. メインプロセス
これは「ボス」となるプロセスです。Node.jsで動作し、アプリのライフサイクルを管理し、オペレーティングシステムへのアクセス権を保持します。ネイティブダイアログの起動、システムトレイの管理、ファイルシステムへの直接アクセスが可能です。
2. レンダラープロセス
これは本質的に、特化したChromeのタブです。 Reactのコード はここで動作します。デフォルトではサンドボックス化されており、ローカルファイルの読み取りやシェルコマンドの実行はできません。この制限によってユーザーの安全が守られています。
3. プリロードスクリプト
これは安全な「エアロック」と考えてください。メインプロセスとレンダラープロセスが、フロントエンドに危険なNode.jsの内部機能を公開することなく通信できるようにします。contextBridgeを使用して、特定の安全な関数のみをReactコンポーネントに渡します。
以下は、src/preload/index.tsのクリーンなセットアップ例です:
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('electronAPI', {
saveFile: (content: string) => ipcRenderer.invoke('dialog:saveFile', content),
onUpdateStatus: (callback: (status: string) => void) =>
ipcRenderer.on('status-changed', (_event, value) => callback(value))
})
実践的なIPC:ファイルの保存
プロセス間通信(IPC)はElectron開発の核心です。ユーザーがテキストファイルをローカルドライブに保存する機能を実装してみましょう。
ステップ1:メインプロセスのハンドラー
src/main/index.tsで、リクエストを待機し、ネイティブの保存ダイアログを開きます:
import { ipcMain, dialog } from 'electron'
import fs from 'fs'
ipcMain.handle('dialog:saveFile', async (event, content: string) => {
const { filePath } = await dialog.showSaveDialog({
title: 'ファイルを保存',
defaultPath: 'note.txt'
})
if (filePath) {
fs.writeFileSync(filePath, content, 'utf-8')
return true
}
return false
})
ステップ2:Reactでの実装
まず、TypeScriptでエラーが出ないようにWindowインターフェースを拡張します。src/renderer/src/env.d.tsを作成します:
interface Window {
electronAPI: {
saveFile: (content: string) => Promise<boolean>
}
}
次に、 Reactコンポーネント から保存処理を実行します:
import { useState } from 'react'
function App() {
const [text, setText] = useState('')
const handleSave = async () => {
const success = await window.electronAPI.saveFile(text)
if (success) alert('ファイルが正常に保存されました!')
}
return (
<div className="p-4">
<textarea
className="w-full border"
onChange={(e) => setText(e.target.value)}
/>
<button onClick={handleSave}>デスクトップに保存</button>
</div>
)
}
配布のためのパッケージ化
優れたアプリも、配布できなければ意味がありません。私は、コード署名や自動更新の煩わしさを簡素化してくれるelectron-builderを使用しています。1つの設定から.exe、.dmg、.AppImageファイルを生成できます。
package.jsonの設定は以下のようになります:
"build": {
"appId": "com.yourname.myapp",
"productName": "My Electron App",
"win": { "target": ["nsis"] },
"mac": { "target": ["dmg"] }
}
npm run build:winまたはnpm run build:macを実行してインストーラーを生成します。アセットによりますが、通常1〜2分で完了します。
開発者のための実践的なアドバイス
- ペイロードに注意: Electronの「Hello World」アプリは、Chromiumを丸ごと同梱するため、約80MBからスタートします。肥大化を避けるため、
node_modulesは軽量に保ちましょう。 - ネイティブなUX: Webのスクロールバーやテキスト選択は、アプリがネイティブでないことを露呈させてしまいます。UI要素にはCSSで
user-select: none;を使用しましょう。また、-webkit-user-drag: none;で画像のドラッグを無効にすると、本物のデスクトップアプリのような操作感になります。 - セキュリティは妥協不可:
nodeIntegration: trueには絶対に設定しないでください。これは重大なセキュリティホールになります。常にプリロードスクリプトを使用して橋渡しを行ってください。 - すべてを非同期に: メインプロセスでは、可能な限り同期的なファイルシステム呼び出し(
fs.readFileSyncなど)を使用しないでください。UI全体がフリーズする原因になります。
Electronは非常にやりがいのあるフレームワークです。すでに持っているスキルを活かして、デスクトップユーザーにアプローチできます。まずは小さなユーティリティから始め、IPC通信をマスターすれば、すぐにプロフェッショナルなソフトウェアをリリースできるようになるでしょう。

