Electron入門|Web技術でデスクトップアプリを構築する方法

kento_morota 19分で読めます

「Web技術(HTML/CSS/JavaScript)でデスクトップアプリを作りたい」——Electronはこの要望を叶えるフレームワークです。VS Code、Slack、Discord、Figmaなど、多くの人気デスクトップアプリがElectronで構築されています。

本記事では、Electronの基本的な仕組みから、プロジェクトのセットアップ、メインプロセスとレンダラープロセスの設計、IPC通信、アプリの配布まで、実践的な開発手順を解説します。

Electronの基本的な仕組み

Electronは、GitHubが開発したオープンソースのフレームワークで、Web技術(HTML、CSS、JavaScript/TypeScript)を使ってWindows・macOS・Linuxのクロスプラットフォームデスクトップアプリケーションを構築できます。

Electronのアーキテクチャ

Electronは、Chromium(Webブラウザのレンダリングエンジン)とNode.jsを組み合わせた構成です。

Chromium:WebページをレンダリングするUI部分を担当します。React、Vue、Svelteなどのフロントエンドフレームワークがそのまま使えます。

Node.js:ファイルシステムへのアクセス、ネットワーク通信、OS固有のAPIの利用など、ネイティブ機能へのアクセスを担当します。

この2つの組み合わせにより、Web開発者が慣れ親しんだ技術スタックで、ネイティブアプリと同等の機能を持つデスクトップアプリを開発できます。

メインプロセスとレンダラープロセス

Electronのアプリケーションは、2種類のプロセスで構成されます。

メインプロセス(Main Process)
アプリケーションのライフサイクル管理、ウィンドウの作成・管理、OSのネイティブAPIへのアクセスを担当するNode.jsプロセスです。1つのアプリケーションにつき1つのメインプロセスが存在します。

レンダラープロセス(Renderer Process)
各ウィンドウのWebコンテンツを描画するChromiumプロセスです。ウィンドウごとに独立したレンダラープロセスが生成されます。セキュリティの観点から、レンダラープロセスはデフォルトでNode.jsのAPIにアクセスできません。

メインプロセスとレンダラープロセス間のデータのやり取りには、IPC(Inter-Process Communication)を使います。

Electronを選ぶべきケース

Electronが適しているプロジェクトは以下のとおりです。

・既存のWeb技術(React、Vue、TypeScript)のスキルを活かしたい
・Windows、macOS、Linuxの全プラットフォームに対応したい
・リッチなUIとネイティブ機能(ファイル操作、通知、メニュー)の両方が必要
・既存のWebアプリをデスクトップアプリ化したい
・プロトタイプを素早く構築したい

開発環境のセットアップ

Electronアプリの開発を始めるための環境構築手順を説明します。2026年現在、Electron ForgeまたはElectron Viteを使ったセットアップが推奨されています。

Electron Forgeでのプロジェクト作成

Electron Forgeは、Electron公式のツールチェーンで、プロジェクトのスキャフォールディングからビルド・配布までを一貫してサポートします。

# プロジェクトの作成(TypeScript + Webpack テンプレート)
npx create-electron-app my-app --template=webpack-typescript

# ディレクトリに移動
cd my-app

# 開発モードで起動
npm start

Electron Viteでのプロジェクト作成

Electron Viteは、Viteベースのビルドツールで、高速なHMR(Hot Module Replacement)と最新のフロントエンドフレームワークとの統合が特徴です。

# プロジェクトの作成
npm create @quick-start/electron my-app -- --template react-ts

# ディレクトリに移動
cd my-app

# 依存パッケージのインストール
npm install

# 開発モードで起動
npm run dev

プロジェクト構成

Electron Viteを使ったプロジェクトの典型的なディレクトリ構成は以下のとおりです。

my-app/
├── src/
│   ├── main/           # メインプロセスのコード
│   │   └── index.ts    # エントリポイント
│   ├── preload/        # プリロードスクリプト
│   │   └── index.ts
│   └── renderer/       # レンダラープロセス(React/Vueアプリ)
│       ├── src/
│       │   ├── App.tsx
│       │   └── main.tsx
│       └── index.html
├── electron.vite.config.ts
├── package.json
└── tsconfig.json

メインプロセスの実装

メインプロセスは、アプリケーションの中核です。ウィンドウの作成、メニューの設定、ネイティブ機能の提供を担当します。

ウィンドウの作成と設定

import { app, BrowserWindow, Menu } from 'electron';
import path from 'path';

function createMainWindow(): BrowserWindow {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      contextIsolation: true,    // セキュリティ:必ずtrue
      nodeIntegration: false,    // セキュリティ:必ずfalse
      sandbox: true,             // セキュリティ:サンドボックス有効化
    },
    titleBarStyle: 'hiddenInset', // macOSのネイティブな見た目
    show: false,                  // 準備完了まで非表示
  });

  // 準備ができたら表示(白画面フラッシュの防止)
  mainWindow.once('ready-to-show', () => {
    mainWindow.show();
  });

  // 開発環境ではlocalhostを表示、本番ではビルド済みファイルを表示
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:5173');
    mainWindow.webContents.openDevTools();
  } else {
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
  }

  return mainWindow;
}

// アプリの起動
app.whenReady().then(() => {
  createMainWindow();

  // macOS:ドックアイコンクリックでウィンドウ再作成
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createMainWindow();
    }
  });
});

// 全ウィンドウが閉じたらアプリを終了(macOS以外)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

アプリケーションメニューの作成

const menuTemplate: Electron.MenuItemConstructorOptions[] = [
  {
    label: 'ファイル',
    submenu: [
      {
        label: '新規作成',
        accelerator: 'CmdOrCtrl+N',
        click: () => { /* 新規作成の処理 */ }
      },
      {
        label: '開く',
        accelerator: 'CmdOrCtrl+O',
        click: () => { /* ファイルを開く処理 */ }
      },
      { type: 'separator' },
      {
        label: '保存',
        accelerator: 'CmdOrCtrl+S',
        click: () => { /* 保存の処理 */ }
      },
      { type: 'separator' },
      { role: 'quit', label: '終了' }
    ]
  },
  {
    label: '編集',
    submenu: [
      { role: 'undo', label: '元に戻す' },
      { role: 'redo', label: 'やり直し' },
      { type: 'separator' },
      { role: 'cut', label: '切り取り' },
      { role: 'copy', label: 'コピー' },
      { role: 'paste', label: '貼り付け' },
    ]
  }
];

const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);

IPC通信の実装

メインプロセスとレンダラープロセス間の通信(IPC)は、Electronアプリの最も重要な設計ポイントです。セキュリティを確保するために、プリロードスクリプトを経由した通信パターンを採用します。

プリロードスクリプト

プリロードスクリプトは、レンダラープロセスに安全なAPIを公開するための橋渡し役です。

// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';

// レンダラープロセスに公開するAPI
contextBridge.exposeInMainWorld('electronAPI', {
  // ファイルの読み込み
  readFile: (filePath: string) =>
    ipcRenderer.invoke('file:read', filePath),

  // ファイルの保存
  saveFile: (filePath: string, content: string) =>
    ipcRenderer.invoke('file:save', filePath, content),

  // ファイル選択ダイアログ
  openFileDialog: () =>
    ipcRenderer.invoke('dialog:openFile'),

  // メインプロセスからの通知を受け取る
  onUpdateAvailable: (callback: (version: string) => void) =>
    ipcRenderer.on('update-available', (_event, version) => callback(version)),
});

メインプロセス側のハンドラー

// src/main/index.ts
import { ipcMain, dialog } from 'electron';
import fs from 'fs/promises';

// ファイル読み込みのハンドラー
ipcMain.handle('file:read', async (_event, filePath: string) => {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    return { success: true, data: content };
  } catch (error) {
    return { success: false, error: (error as Error).message };
  }
});

// ファイル保存のハンドラー
ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
  try {
    await fs.writeFile(filePath, content, 'utf-8');
    return { success: true };
  } catch (error) {
    return { success: false, error: (error as Error).message };
  }
});

// ファイル選択ダイアログのハンドラー
ipcMain.handle('dialog:openFile', async () => {
  const result = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [
      { name: 'テキストファイル', extensions: ['txt', 'md', 'json'] },
      { name: 'すべてのファイル', extensions: ['*'] },
    ],
  });
  return result.canceled ? null : result.filePaths[0];
});

レンダラープロセスでのAPI利用

// src/renderer/src/App.tsx
function App() {
  const [content, setContent] = useState('');

  const handleOpenFile = async () => {
    const filePath = await window.electronAPI.openFileDialog();
    if (filePath) {
      const result = await window.electronAPI.readFile(filePath);
      if (result.success) {
        setContent(result.data);
      }
    }
  };

  return (
    <div>
      <button onClick={handleOpenFile}>ファイルを開く</button>
      <textarea value={content} onChange={(e) => setContent(e.target.value)} />
    </div>
  );
}

セキュリティ対策

Electronアプリのセキュリティは、Web開発以上に注意が必要です。デスクトップアプリはファイルシステムやOSのAPIにアクセスできるため、脆弱性があると深刻な被害につながります。

必須のセキュリティ設定

Context Isolationを有効にする
contextIsolation: trueを設定し、レンダラープロセスからNode.jsのAPIに直接アクセスできないようにします。これはElectronのデフォルト設定ですが、意図的に無効化しないよう注意しましょう。

Node Integrationを無効にする
nodeIntegration: falseを設定し、レンダラープロセスでrequire()やNode.jsモジュールを直接使えないようにします。

サンドボックスを有効にする
sandbox: trueを設定し、レンダラープロセスのOSリソースへのアクセスを制限します。

Content Security Policy(CSP)の設定
HTMLのmetaタグまたはHTTPヘッダーでCSPを設定し、スクリプトの実行元を制限します。

<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
/>

避けるべきパターン

・レンダラープロセスで外部URLを直接読み込まない(フィッシングやXSSのリスク)
・shell.openExternal()にユーザー入力のURLをそのまま渡さない(任意コマンド実行のリスク)
・プリロードスクリプトで公開するAPIを最小限にする(過剰な権限の付与を避ける)
・electronのバージョンを常に最新に保ち、既知の脆弱性を放置しない

アプリのビルドと配布

開発が完了したら、アプリをビルドして配布します。Electron ForgeやElectron Builderを使って、各プラットフォーム向けのインストーラーを作成できます。

Electron Forgeでのビルド

# 各プラットフォーム向けのパッケージを作成
npm run make

# 出力先
# out/make/squirrel.windows/   → Windows用(.exeインストーラー)
# out/make/dmg/                 → macOS用(.dmgファイル)
# out/make/deb/                 → Linux用(.debパッケージ)

自動アップデートの実装

デスクトップアプリでは、自動アップデート機能が重要です。electron-updaterライブラリを使うと、GitHubリリースやS3から自動的にアップデートを配信できます。

import { autoUpdater } from 'electron-updater';
import { BrowserWindow } from 'electron';

function setupAutoUpdater(mainWindow: BrowserWindow) {
  // アップデートの確認
  autoUpdater.checkForUpdatesAndNotify();

  // アップデートが利用可能
  autoUpdater.on('update-available', (info) => {
    mainWindow.webContents.send('update-available', info.version);
  });

  // ダウンロード完了
  autoUpdater.on('update-downloaded', () => {
    // ユーザーに確認後、再起動してインストール
    autoUpdater.quitAndInstall();
  });

  // 定期的にアップデートを確認(1時間ごと)
  setInterval(() => {
    autoUpdater.checkForUpdatesAndNotify();
  }, 60 * 60 * 1000);
}

コード署名

macOSとWindowsでは、アプリにコード署名を施すことが推奨されます。署名がないアプリは、OSが「不明な開発者のアプリ」として警告を表示し、ユーザーが起動できない場合があります。

macOS:Apple Developer Programに登録し、Developer ID証明書で署名
Windows:コードサイニング証明書を取得して署名

Electronの代替技術との比較

Electron以外にもデスクトップアプリを構築する選択肢があります。用途に応じて検討しましょう。

Tauri

Rust製のフレームワークで、Electronよりも軽量なアプリを構築できます。ChromiumではなくOSのWebViewを使用するため、バイナリサイズが小さく、メモリ使用量も少ないです。パフォーマンスとリソース効率を重視する場合はTauriが有力な選択肢です。ただし、Rustの知識が必要な場面があり、Electronほどエコシステムが成熟していません。

Flutter Desktop

Dartで開発し、Windows/macOS/Linux向けのネイティブアプリを構築できます。モバイルアプリとデスクトップアプリでコードを共有したい場合に適しています。

選定の指針

Web技術の資産を活かしたい:Electron
軽量さとパフォーマンスを重視:Tauri
モバイルとデスクトップの両方:Flutter Desktop
エコシステムと情報量の豊富さ:Electron

まとめ

Electronは、Web技術でクロスプラットフォームのデスクトップアプリを構築する最も実績のあるフレームワークです。本記事のポイントを整理します。

・ElectronはChromium + Node.jsでWeb技術をデスクトップアプリに変換する
・メインプロセス(Node.js)とレンダラープロセス(Chromium)の役割分担を理解する
・IPC通信はプリロードスクリプトを経由した安全なパターンで実装する
・contextIsolation、nodeIntegration無効化、サンドボックスでセキュリティを確保する
・Electron ForgeまたはElectron Viteで開発環境を素早くセットアップできる
・electron-updaterで自動アップデート機能を実装する
・軽量さを重視する場合はTauriも検討する

まずはElectron Viteでプロジェクトを作成し、簡単なファイルエディタやTODOアプリを構築してみてください。Web開発のスキルがそのまま活かせることを実感できるはずです。

#Electron#デスクトップアプリ#JavaScript
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

AI活用のヒントをお探しですか?お気軽にご相談ください。

まずは話だけ聞いてもらう