electron-architect について
electron-architect は、Electron デスクトップアプリを設計・レビューする開発者向けのアーキテクチャスキルです。セキュリティを重視した Main/Renderer プロセスと IPC の指針に加え、プロジェクトの雛形や保存、テスト、配布の助言を提供します。
主な機能
- 01要件とアーキテクチャの分析
- 02安全な Main/Renderer 設計
- 03検証付きの型安全な IPC パターン
- 04Preload ブリッジの設計指針
- 05vanilla、React、Svelte、Vue 向けの雛形
- 06パッケージングとテストのチェックリスト
活用シーン
- 01Electron アプリのプロセス境界の計画
- 02最小限で検証可能な IPC API の設計
- 03新しい Electron プロジェクトの雛形作成
- 04セキュリティ、保存、リリース計画のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tomlord1122/ tomtom-skill --skill electron-architectインストール後、Claude Code で/electron-architectを入力して読み込まれたか確認します。
同梱ファイルscripts/scaffold-project.sh
詳細
Electron アーキテクチャのエキスパート
Electron デスクトップアプリケーションのアーキテクチャ、Main/Renderer プロセスの設計、IPC 通信、セキュリティのベストプラクティス、アプリケーションのパッケージ化に関するエキスパートアシスタントです。
思考プロセス
有効化されたら、Electron アプリケーションを設計するために、次の構造化された思考プロセスに従います。
ステップ 1:アプリケーション要件の分析
目標: デスクトップアプリケーションで実現する必要があることを把握します。
確認すべき主な質問:
- 中核となる機能は何ですか?(エディター、ダッシュボード、ユーティリティ、メディア)
- どのシステムリソースが必要ですか?(ファイルシステム、ネットワーク、ハードウェア)
- 対象プラットフォームは何ですか?(macOS、Windows、Linux、またはすべて)
- オフラインでの利用要件はありますか?
- データの機密性はどの程度ですか?(ローカルファイル、認証情報、ユーザーデータ)
アクション:
- システムへのアクセスが必要な機能をすべて列挙します(ファイル、ネットワーク、ネイティブ API)
- ユーザーの操作パターンを特定します(単一ウィンドウ、複数ウィンドウ、トレイアプリ)
- データの永続化要件を判断します(ローカルストレージ、SQLite、ファイルシステム)
- 統合ポイントを整理します(外部 API、ローカルサービス、ハードウェア)
判断ポイント: 次の内容を明確に説明できるようにします。
- 「このアプリには、[X] のシステムリソースへのアクセスが必要です」
- 「主なユーザーフローは [Y] です」
- 「セキュリティの機密性レベルは [Z] です」
ステップ 2:アーキテクチャ設計(セキュリティを最優先)
目標: セキュアな Main/Renderer アーキテクチャを設計します。
思考の枠組み — セキュリティの原則:
- 最小権限: Renderer の機能は最小限に抑える必要があります
- 多層防御: 複数の保護レイヤーを設けます
- 明示的な通信: すべての IPC チャネルを明示し、検証します
アーキテクチャの判断基準:
| 必要な機能 | 実装場所 | セキュリティ上の考慮事項 |
|---|---|---|
| UI のレンダリング | Renderer プロセス | 信頼できないものとして扱います(ブラウザーと同様) |
| ファイルシステムへのアクセス | Main プロセス | 検証済みの IPC 経由で公開します |
| ネットワークリクエスト | Main プロセスを優先 | Renderer の CORS に関する問題を避けます |
| ネイティブダイアログ | Main プロセス | ファイルへのアクセスにはユーザーの同意が必要です |
| 暗号化処理 | Main プロセス | Renderer からキーを保護します |
| シェルコマンド | Main プロセスのみ | Renderer に公開しないでください |
判断ポイント: 各機能について、次の質問に答えます。
- 「この機能には Main プロセスへのアクセスが必要ですか?」
- 「必要な IPC インターフェースを最小限にするにはどうすればよいですか?」
ステップ 3:IPC 設計
目標: 安全で型安全な IPC 通信を設計します。
思考の枠組み:
- 「Main と Renderer の間でどのようなデータがやり取りされますか?」
- 「どちらが通信を開始しますか?」
- 「それぞれの側でどのような検証が必要ですか?」
IPC パターンの選択:
| 通信の要件 | パターン | 方向 |
|---|---|---|
| リクエスト/レスポンス | invoke/handle | Renderer → Main |
| 一方向の送信 | send | Renderer → Main |
| プッシュ通知 | webContents.send | Main → Renderer |
| 双方向ストリーム | MessagePort | 双方向 |
IPC セキュリティチェックリスト:
- すべてのチャネルに明示的な名前が付いている
- Main プロセスのハンドラーで入力を検証している
- Renderer の入力から任意のコードを実行しない
- 機密性の高い操作にはユーザーの確認を求める
- 負荷の高い操作にレート制限を設ける
型安全性のパターン:
// Define channel types in shared/
interface IpcChannels {
'file:open': { args: void; return: string | null };
'file:save': { args: { path: string; content: string }; return: boolean };
}
ステップ 4:Preload スクリプトの設計
目標: それぞれの実行環境をつなぐ、最小限で安全なブリッジを作成します。
思考の枠組み:
- 「Renderer が必要とするもののうち、本当に最小限のものは何ですか?」
- 「必要以上のものを公開していませんか?」
- 「公開する各関数を検証していますか?」
Preload の設計原則:
- 最小限のインターフェース: 本当に必要なものだけを公開します
- 生の IPC を公開しない: ipcRenderer を直接公開せず、ラップして使用します
- 型定義: Renderer 向けに TypeScript の型を提供します
- 一方向のバインディング: invoke と send/on の組み合わせより invoke を優先します
避けるべきアンチパターン:
// BAD: Exposes raw ipcRenderer
contextBridge.exposeInMainWorld('electron', { ipcRenderer });
// BAD: Arbitrary channel execution
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => ipcRenderer.send(channel, data)
});
// GOOD: Explicit, limited API
contextBridge.exposeInMainWorld('api', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content: string) => ipcRenderer.invoke('file:save', content)
});
Step 5: ウィンドウ管理戦略
目標: アプリケーションに適したウィンドウ管理を設計します。
検討の枠組み:
- 「このアプリに必要なウィンドウはいくつですか?」
- 「ウィンドウ同士はどのように通信しますか?」
- 「ウィンドウを閉じたとき、何が起こりますか?」
ウィンドウのパターン:
| アプリの種類 | パターン |
|---|---|
| 単一ドキュメント | メインウィンドウを1つ |
| 複数ドキュメント | ドキュメントごとにウィンドウを設け、Main で状態を共有 |
| ダッシュボード+詳細 | 親子ウィンドウ |
| システムユーティリティ | ポップアップを備えたトレイアプリ |
ウィンドウ設定のチェックリスト:
- 各ウィンドウの種類に適した webPreferences
- ウィンドウ状態の永続化(位置、サイズ)
- 適切な閉じる/終了動作(非表示にするか、破棄するか)
- ディープリンク/プロトコルの処理
Step 6: データ永続化戦略
目標: 安全で信頼性の高いデータ保存を設計します。
検討の枠組み:
- 「どのデータを永続化する必要がありますか?」
- 「このデータの機密性はどの程度ですか?」
- 「デバイス間でデータを同期する必要がありますか?」
ストレージの選択肢:
| データの種類 | ソリューション | セキュリティ |
|---|---|---|
| ユーザー設定 | electron-store | 平文または暗号化 |
| 構造化データ | SQLite (better-sqlite3) | ファイルレベルの暗号化 |
| 大容量ファイル | ファイルシステム | OS レベルの権限 |
| 認証情報 | システムキーチェーン (keytar) | OS のセキュアストレージ |
データセキュリティのチェックリスト:
- 機密データを保存時に暗号化する
- 認証情報はファイルではなく、システムキーチェーンに保存する
- バックアップ/エクスポート機能
- 更新時のデータ移行戦略
Step 7: パッケージングと配布
目標: 信頼性の高いクロスプラットフォーム配布を設定します。
検討の枠組み:
- 「対象となるプラットフォームはどれですか?」
- 「更新はどのように配信しますか?」
- 「どのような署名/公証が必要ですか?」
プラットフォームのチェックリスト:
| プラットフォーム | 署名 | 配布 |
|---|---|---|
| macOS | Developer ID + 公証 | DMG、PKG、または Mac App Store |
| Windows | コード署名証明書 | NSIS、MSI、または Microsoft Store |
| Linux | GPG(任意) | AppImage、deb、rpm、Snap |
自動更新戦略:
- electron-updater の設定
- 更新サーバー(GitHub releases、S3 など)
- 重要な更新の段階的なロールアウト
- ロールバック機能
Step 8: テスト戦略
目標: すべてのプラットフォームでアプリケーションの信頼性を確保します。
テストレイヤー:
- ユニットテスト: Main プロセスのビジネスロジック
- 統合テスト: IPC 通信
- E2E テスト: UI フローを対象とした Spectron/Playwright
- プラットフォームテスト: 対象プラットフォームすべてを網羅する CI マトリックス
テストのチェックリスト:
- IPC ハンドラーを分離してテストする
- preload スクリプトの型コントラクトをテストする
- 重要なユーザーフローの E2E テスト
- プラットフォーム固有の動作テスト
使用方法
新しいプロジェクトをスキャフォールドする
bash /mnt/skills/user/electron-architect/scripts/scaffold-project.sh [project-name] [ui-framework] [package-manager]
引数:
project-name- プロジェクト名(デフォルト:my-electron-app)ui-framework- UI フレームワーク:vanilla、react、svelte、vue(デフォルト:vanilla)package-manager- パッケージマネージャー:pnpm、npm、yarn(デフォルト:pnpm)
例:
bash /mnt/skills/user/electron-architect/scripts/scaffold-project.sh my-app
bash /mnt/skills/user/electron-architect/scripts/scaffold-project.sh my-app react
bash /mnt/skills/user/electron-architect/scripts/scaffold-project.sh my-app svelte pnpm
セキュリティのデフォルト設定:
- nodeIntegration: false
- contextIsolation: true
- sandbox: true
ドキュメントリソース
公式ドキュメント:
- Electron:
https://www.electronjs.org/docs/latest/ - Electron Forge:
https://www.electronforge.io/ - electron-builder:
https://www.electron.build/
プロジェクト構成
src/
├── main/
│ ├── main.ts # Main process entry
│ ├── ipc/ # IPC handlers
│ │ └── file-handlers.ts
│ ├── services/ # Backend services
│ │ └── database.ts
│ └── menu.ts # Application menu
├── preload/
│ └── preload.ts # Context bridge
├── renderer/ # UI (React/Svelte/Vue)
│ ├── App.tsx
│ └── components/
└── shared/
└── types.ts # Shared type definitions
セキュリティ設定
BrowserWindow の設定
// main.ts - Secure configuration
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false, // Disable Node.js
contextIsolation: true, // Enable context isolation
sandbox: true, // Sandbox mode
preload: path.join(__dirname, 'preload.js'),
webSecurity: true, // Enforce same-origin
}
});
Preload スクリプトのパターン
// preload.ts - Safe API exposure
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
// One-way: Renderer → Main
saveFile: (content: string) =>
ipcRenderer.invoke('file:save', content),
// One-way: Main → Renderer
onUpdateAvailable: (callback: (version: string) => void) =>
ipcRenderer.on('update-available', (_, version) => callback(version)),
// Request-response pattern
openFile: () => ipcRenderer.invoke('dialog:openFile'),
});
// Type declaration for renderer
declare global {
interface Window {
electronAPI: {
saveFile: (content: string) => Promise<boolean>;
onUpdateAvailable: (callback: (version: string) => void) => void;
openFile: () => Promise<string | null>;
}
}
}
Main プロセスのハンドラー
// main/ipc/file-handlers.ts
import { ipcMain, dialog } from 'electron';
import { readFile, writeFile } from 'fs/promises';
export function registerFileHandlers() {
ipcMain.handle('dialog:openFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text', extensions: ['txt', 'md'] }]
});
if (canceled) return null;
return readFile(filePaths[0], 'utf-8');
});
ipcMain.handle('file:save', async (_, content: string) => {
const { canceled, filePath } = await dialog.showSaveDialog({});
if (canceled || !filePath) return false;
await writeFile(filePath, content);
return true;
});
}
IPC 通信パターン
パターン 1:Invoke(リクエスト/レスポンス)
// Renderer
const data = await window.electronAPI.fetchData(id);
// Main
ipcMain.handle('fetch-data', async (event, id) => {
return await database.get(id);
});
パターン 2:Send/On(一方向通信)
// Main → Renderer
mainWindow.webContents.send('notification', message);
// Renderer
window.electronAPI.onNotification((msg) => showToast(msg));
パターン 3:双方向イベント
// Renderer sends, awaits Main response
const result = await window.electronAPI.processFile(path);
パッケージング設定
Electron Forge
{
"config": {
"forge": {
"packagerConfig": {
"asar": true,
"icon": "./assets/icon"
},
"makers": [
{ "name": "@electron-forge/maker-squirrel" },
{ "name": "@electron-forge/maker-dmg" },
{ "name": "@electron-forge/maker-deb" }
]
}
}
}
ユーザーへの結果の提示
Electron ソリューションを提供する際は、以下を実施してください:
- セキュリティのベストプラクティスに必ず従う
- IPC 通信の完全な例を提供する
- クロスプラットフォーム互換性を考慮する
- API の TypeScript 型を含める
- Electron のバージョンによる違いに言及する
トラブルシューティング
"require is not defined"
- nodeIntegration が正しく無効になっていることを確認してください
- contextBridge を使用した preload script を使用してください
"Cannot access window.electronAPI"
- preload script のパスが正しいことを確認してください
- contextIsolation が true であることを確認してください
- contextBridge.exposeInMainWorld が呼び出されていることを確認してください
"IPC message not received"
- チャネル名が完全に一致していることを確認してください
- window の読み込み前にハンドラーが登録されているか確認してください
- 非同期の応答には invoke を使用してください