mcpField
E
electron-architect

electron-architect

github.com/tomlord1122開発0150 回インストール6 週間前に更新
リポジトリ

electron-architect について

electron-architect は、Electron デスクトップアプリを設計・レビューする開発者向けのアーキテクチャスキルです。セキュリティを重視した Main/Renderer プロセスと IPC の指針に加え、プロジェクトの雛形や保存、テスト、配布の助言を提供します。

主な機能

  1. 01要件とアーキテクチャの分析
  2. 02安全な Main/Renderer 設計
  3. 03検証付きの型安全な IPC パターン
  4. 04Preload ブリッジの設計指針
  5. 05vanilla、React、Svelte、Vue 向けの雛形
  6. 06パッケージングとテストのチェックリスト

活用シーン

  1. 01Electron アプリのプロセス境界の計画
  2. 02最小限で検証可能な IPC API の設計
  3. 03新しい Electron プロジェクトの雛形作成
  4. 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、またはすべて)
  • オフラインでの利用要件はありますか?
  • データの機密性はどの程度ですか?(ローカルファイル、認証情報、ユーザーデータ)

アクション:

  1. システムへのアクセスが必要な機能をすべて列挙します(ファイル、ネットワーク、ネイティブ API)
  2. ユーザーの操作パターンを特定します(単一ウィンドウ、複数ウィンドウ、トレイアプリ)
  3. データの永続化要件を判断します(ローカルストレージ、SQLite、ファイルシステム)
  4. 統合ポイントを整理します(外部 API、ローカルサービス、ハードウェア)

判断ポイント: 次の内容を明確に説明できるようにします。

  • 「このアプリには、[X] のシステムリソースへのアクセスが必要です」
  • 「主なユーザーフローは [Y] です」
  • 「セキュリティの機密性レベルは [Z] です」
ステップ 2:アーキテクチャ設計(セキュリティを最優先)

目標: セキュアな Main/Renderer アーキテクチャを設計します。

思考の枠組み — セキュリティの原則:

  1. 最小権限: Renderer の機能は最小限に抑える必要があります
  2. 多層防御: 複数の保護レイヤーを設けます
  3. 明示的な通信: すべての IPC チャネルを明示し、検証します

アーキテクチャの判断基準:

必要な機能実装場所セキュリティ上の考慮事項
UI のレンダリングRenderer プロセス信頼できないものとして扱います(ブラウザーと同様)
ファイルシステムへのアクセスMain プロセス検証済みの IPC 経由で公開します
ネットワークリクエストMain プロセスを優先Renderer の CORS に関する問題を避けます
ネイティブダイアログMain プロセスファイルへのアクセスにはユーザーの同意が必要です
暗号化処理Main プロセスRenderer からキーを保護します
シェルコマンドMain プロセスのみRenderer に公開しないでください

判断ポイント: 各機能について、次の質問に答えます。

  • 「この機能には Main プロセスへのアクセスが必要ですか?」
  • 「必要な IPC インターフェースを最小限にするにはどうすればよいですか?」
ステップ 3:IPC 設計

目標: 安全で型安全な IPC 通信を設計します。

思考の枠組み:

  • 「Main と Renderer の間でどのようなデータがやり取りされますか?」
  • 「どちらが通信を開始しますか?」
  • 「それぞれの側でどのような検証が必要ですか?」

IPC パターンの選択:

通信の要件パターン方向
リクエスト/レスポンスinvoke/handleRenderer → Main
一方向の送信sendRenderer → Main
プッシュ通知webContents.sendMain → 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 の設計原則:

  1. 最小限のインターフェース: 本当に必要なものだけを公開します
  2. 生の IPC を公開しない: ipcRenderer を直接公開せず、ラップして使用します
  3. 型定義: Renderer 向けに TypeScript の型を提供します
  4. 一方向のバインディング: 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: パッケージングと配布

目標: 信頼性の高いクロスプラットフォーム配布を設定します。

検討の枠組み:

  • 「対象となるプラットフォームはどれですか?」
  • 「更新はどのように配信しますか?」
  • 「どのような署名/公証が必要ですか?」

プラットフォームのチェックリスト:

プラットフォーム署名配布
macOSDeveloper ID + 公証DMG、PKG、または Mac App Store
Windowsコード署名証明書NSIS、MSI、または Microsoft Store
LinuxGPG(任意)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 を使用してください

コメント