T-CREATOR

Electron セットアップ最短ルート:Vite + TypeScript + ESLint + Preload 分離

Electron セットアップ最短ルート:Vite + TypeScript + ESLint + Preload 分離

Electron アプリ開発において、従来の Webpack ベースの環境構築は複雑で時間がかかるものでした。しかし、現在ではVite + TypeScript + ESLintの組み合わせにより、セキュリティを保ちながら最短ルートでの開発環境構築が可能です。

この記事では、最新のツールチェーンを活用して効率的な Electron アプリ開発環境を構築する手順をわかりやすく解説いたします。特にPreload スクリプトの適切な分離も含めて、実践的なセットアップ方法をご紹介いたします。

背景

従来の Electron セットアップの煩雑さ

Electron アプリ開発の従来のアプローチでは、Webpack の複雑な設定が必要でした。メインプロセス、レンダラープロセス、Preload スクリプトのそれぞれに異なる設定が必要で、初心者にとっては非常に高いハードルとなっていました。

設定ファイルが複数に分かれ、依存関係の管理も煩雑になりがちでした。また、開発サーバーの起動に時間がかかり、コード変更時のホットリロードも遅いという問題がありました。

Vite 登場による開発体験の向上

2020 年にリリースされた Vite は、ES Modules を活用した高速な開発サーバーを提供します。従来の Webpack と比較して、以下の図のような大幅な改善を実現しています。

mermaidflowchart LR
  webpack["Webpack<br/>バンドル型"] -->|"遅い"| dev1["開発サーバー<br/>起動時間: 数分"]
  vite["Vite<br/>ESM型"] -->|"高速"| dev2["開発サーバー<br/>起動時間: 数秒"]

  dev1 --> reload1["ホットリロード<br/>数秒〜数十秒"]
  dev2 --> reload2["ホットリロード<br/>ミリ秒単位"]

この図が示すように、Vite を使用することで開発効率が劇的に向上しています。

TypeScript 導入による型安全性の重要性

Electron アプリでは、メインプロセスとレンダラープロセス間での IPC 通信が頻繁に発生します。このような複雑な処理において、型安全性は非常に重要です。

TypeScript を導入することで、コンパイル時にエラーを検出でき、実行時エラーを大幅に減らすことができます。特に、Electron の複雑な API 呼び出しにおいて、型補完により開発者の生産性も向上します。

セキュリティ観点からの Preload 分離の必要性

Electron アプリのセキュリティにおいて Preload スクリプトの適切な分離は必須です。Context Isolation を有効にし、Preload スクリプトを通じて安全に Node.js API にアクセスする仕組みを構築する必要があります。

以下の図は、安全なアーキテクチャの基本構造を示しています。

mermaidflowchart TD
  renderer["レンダラープロセス<br/>(Webページ)"] --> preload["Preloadスクリプト<br/>(分離された環境)"]
  preload --> main["メインプロセス<br/>(Node.js API)"]

  renderer -.->|"直接アクセス禁止"| main

  subgraph "Context Isolation"
    preload
  end

  subgraph "セキュリティ境界"
    renderer
  end

この分離により、レンダラープロセスから直接 Node.js API にアクセスすることを防ぎ、セキュリティリスクを大幅に軽減できます。

課題

従来の Webpack ベースセットアップの複雑性

従来の Electron アプリ開発では、Webpack の設定が非常に複雑でした。メインプロセス用、レンダラープロセス用、Preload スクリプト用の 3 つの異なる設定ファイルが必要で、それぞれに異なるローダーやプラグインの設定が求められます。

この複雑性により、プロジェクトの初期セットアップに多大な時間がかかり、設定ミスによるトラブルも頻発していました。特に初心者にとっては、どの設定がどのプロセスに影響するかを理解することが困難でした。

開発サーバーの起動時間の長さ

Webpack ベースの開発環境では、初回起動時にすべてのモジュールをバンドルする必要があります。プロジェクトが大きくなるにつれて、この処理に数分かかることも珍しくありませんでした。

コード変更時のホットリロードも同様に時間がかかり、開発者の集中力を削ぐ要因となっていました。以下の表は、プロジェクトサイズごとの起動時間の目安を示しています。

プロジェクトサイズWebpack 起動時間Vite 起動時間
小規模(~50 ファイル)30 秒〜1 分2〜5 秒
中規模(~200 ファイル)1〜3 分3〜8 秒
大規模(500 ファイル+)3〜10 分5〜15 秒

TypeScript 設定の難しさ

Electron アプリで TypeScript を使用する場合、メインプロセスとレンダラープロセスで異なる実行環境に対応する必要があります。Node.js 環境のメインプロセスとブラウザ環境のレンダラープロセスでは、利用可能な API が大きく異なるためです。

適切な型定義ファイルの設定や、プロセス間での型共有の仕組みを構築することは、経験豊富な開発者でも時間がかかる作業でした。

Preload スクリプトの適切な分離方法の不明瞭さ

Electron のセキュリティベストプラクティスでは、Preload スクリプトを使用してレンダラープロセスとメインプロセス間の安全な通信を実現する必要があります。しかし、どのように実装すべきかが不明瞭で、多くの開発者が適切でない方法を採用してしまうケースが見られました。

特に以下のような問題が頻発していました:

  • Context Isolation の設定不備
  • 不適切な IPC 通信の実装
  • セキュリティホールを生む危険な直接アクセス

これらの課題により、安全で効率的な Electron アプリの開発は非常に困難なものとなっていました。

解決策

Vite を活用した高速開発環境の構築

Vite の導入により、従来の Webpack ベースの複雑な設定を大幅に簡素化できます。Vite は設定ゼロでも動作するため、最小限の設定で Electron アプリの開発を開始できます。

以下の図は、Vite ベースの新しい開発フローを示しています。

mermaidflowchart TB
  start["プロジェクト開始"] --> install["yarn create electron-vite"]
  install --> config["簡単な設定<br/>(1ファイルのみ)"]
  config --> dev["yarn dev<br/>(数秒で起動)"]
  dev --> coding["コーディング"]
  coding --> hmr["HMR<br/>(瞬時に反映)"]
  hmr --> coding

この図で分かるように、Vite ベースのワークフローは非常にシンプルで効率的です。

Vite の主な利点は以下の通りです:

  • 高速な開発サーバー起動: ES Modules を活用し、数秒で起動
  • 瞬時のホットモジュールリプレースメント: コード変更が即座に反映
  • 最小限の設定: 複雑な Webpack 設定が不要
  • 豊富なプラグインエコシステム: TypeScript、ESLint などの統合が簡単

TypeScript による型安全なコード品質の確保

TypeScript の導入により、Electron アプリ特有の複雑なプロセス間通信においても型安全性を確保できます。特に重要なのは、IPC 通信における型共有の仕組みです。

型安全性の確保により以下のメリットが得られます:

  • コンパイル時エラー検出: 実行前にバグを発見
  • 優れた開発者体験: 型補完によるコーディング効率向上
  • リファクタリングの安全性: 大規模な変更でも型チェックで安心
  • ドキュメント代わり: 型定義自体が API ドキュメントの役割

ESLint によるコード品質管理

ESLint の設定により、チーム開発における一貫したコード品質を維持できます。特に Electron アプリでは、セキュリティに関するルールセットの適用が重要です。

推奨する設定の組み合わせは以下の通りです:

設定項目推奨パッケージ目的
基本ルール@typescript-eslint​/​recommendedTypeScript 基本ルール
セキュリティeslint-plugin-securityセキュリティホール検出
Electron 固有eslint-plugin-electronElectron 特有の問題対応
インポート管理eslint-plugin-importモジュール管理の最適化

セキュリティベストプラクティスに基づく Preload 分離

適切な Preload スクリプトの分離により、セキュリティを保ちながら必要な機能にアクセスできる仕組みを構築します。以下の図は、推奨するアーキテクチャパターンを示しています。

mermaidflowchart LR
  subgraph "レンダラープロセス"
    web["Webアプリ<br/>(React/Vue等)"]
  end

  subgraph "Preload領域"
    api["window.electronAPI<br/>(型安全なAPI)"]
  end

  subgraph "メインプロセス"
    main["Node.js API<br/>(ファイルシステム等)"]
  end

  web --> api
  api --> main
  main --> api
  api --> web

この構成により、以下のセキュリティ要件を満たします:

  • Context Isolation 有効: レンダラープロセスの完全分離
  • Node Integration 無効: 直接的な Node.js API アクセスを禁止
  • 型安全な API: Preload を通じた制御されたアクセス
  • 最小権限の原則: 必要最小限の機能のみ公開

具体例

それでは、実際に Vite + TypeScript + ESLint + Preload 分離を活用した Electron アプリを構築していきましょう。順を追って詳しく解説いたします。

プロジェクト初期化

まず、プロジェクトの初期化から始めます。electron-vite テンプレートを使用することで、最短時間でのセットアップが可能です。

bash# プロジェクトの作成
yarn create electron-vite my-electron-app
cd my-electron-app

この一つのコマンドで、以下の構成が自動的に作成されます:

  • Vite 設定ファイル
  • TypeScript 設定
  • 基本的な ESLint 設定
  • Electron の基本構造

作成されるディレクトリ構造は以下のようになります:

perlmy-electron-app/
├── src/
│   ├── main/           # メインプロセス
│   ├── preload/        # Preloadスクリプト
│   └── renderer/       # レンダラープロセス
├── electron.vite.config.ts
├── package.json
└── tsconfig.json

Vite 設定

electron.vite.config.ts ファイルで Vite の設定を行います。このファイル一つで全プロセスの設定を管理できるのが Vite の大きな利点です。

typescriptimport { resolve } from 'path';
import {
  defineConfig,
  externalizeDepsPlugin,
} from 'electron-vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  main: {
    plugins: [externalizeDepsPlugin()],
  },
  preload: {
    plugins: [externalizeDepsPlugin()],
  },
  renderer: {
    resolve: {
      alias: {
        '@renderer': resolve('src/renderer/src'),
      },
    },
    plugins: [react()],
  },
});

この設定により、以下の機能が有効になります:

  • externalizeDepsPlugin: Node.js 依存関係の外部化
  • エイリアス設定: インポートパスの簡略化
  • React 統合: レンダラープロセスで React を使用可能

TypeScript 設定

TypeScript の設定では、各プロセスに応じた適切な型環境を構築します。tsconfig.json ファイルを以下のように設定します:

json{
  "extends": "@electron-toolkit/tsconfig/tsconfig.json",
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@main/*": ["src/main/*"],
      "@preload/*": ["src/preload/*"],
      "@renderer/*": ["src/renderer/*"]
    }
  },
  "include": ["src/**/*", "electron.vite.config.*"]
}

各プロセス専用の型定義ファイルも作成します:

typescript// src/types/electron.d.ts
export interface IElectronAPI {
  openFile: () => Promise<string>;
  saveFile: (content: string) => Promise<void>;
  onMenuClick: (callback: (menuId: string) => void) => void;
}

declare global {
  interface Window {
    electronAPI: IElectronAPI;
  }
}

この設定により、レンダラープロセスから Preload API に型安全にアクセスできるようになります。

ESLint 設定

セキュリティとコード品質を重視した ESLint 設定を行います。.eslintrc.js ファイルを以下のように設定します:

javascriptmodule.exports = {
  extends: [
    '@electron-toolkit/eslint-config-ts',
    '@electron-toolkit/eslint-config-prettier',
  ],
  rules: {
    // Electron特有のセキュリティルール
    '@typescript-eslint/explicit-function-return-type':
      'error',
    'import/no-nodejs-modules': [
      'error',
      {
        allow: ['path', 'fs'],
      },
    ],
    // 危険なAPIの使用を禁止
    'no-eval': 'error',
    'no-implied-eval': 'error',
  },
};

package.json に lint スクリプトを追加します:

json{
  "scripts": {
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
  }
}

Preload 分離実装

最も重要な Preload スクリプトの実装を行います。セキュリティを保ちながら必要な機能を提供する仕組みを構築します。

まず、メインプロセスでの IPC 設定を行います:

typescript// src/main/index.ts
import {
  app,
  shell,
  BrowserWindow,
  ipcMain,
  dialog,
} from 'electron';
import { join } from 'path';
import {
  electronApp,
  optimizer,
  is,
} from '@electron-toolkit/utils';
import icon from '../../resources/icon.png?asset';

function createWindow(): void {
  const mainWindow = new BrowserWindow({
    width: 900,
    height: 670,
    show: false,
    autoHideMenuBar: true,
    ...(process.platform === 'linux' ? { icon } : {}),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  mainWindow.on('ready-to-show', () => {
    mainWindow.show();
  });

  // IPCハンドラーの設定
  ipcMain.handle('dialog:openFile', async () => {
    const result = await dialog.showOpenDialog(mainWindow, {
      properties: ['openFile'],
      filters: [
        { name: 'Text Files', extensions: ['txt'] },
      ],
    });
    return result.filePaths[0];
  });
}

次に、Preload スクリプトで安全な API を提供します:

typescript// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
import { electronAPI } from '@electron-toolkit/preload';

// セキュリティを重視したAPI設計
const api = {
  openFile: (): Promise<string> =>
    ipcRenderer.invoke('dialog:openFile'),

  saveFile: (content: string): Promise<void> =>
    ipcRenderer.invoke('dialog:saveFile', content),

  onMenuClick: (
    callback: (menuId: string) => void
  ): void => {
    ipcRenderer.on('menu-click', (_event, menuId) =>
      callback(menuId)
    );
  },

  removeAllListeners: (channel: string): void => {
    ipcRenderer.removeAllListeners(channel);
  },
};

// 型安全なAPIの公開
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld(
      'electron',
      electronAPI
    );
    contextBridge.exposeInMainWorld('electronAPI', api);
  } catch (error) {
    console.error(error);
  }
} else {
  window.electron = electronAPI;
  window.electronAPI = api;
}

レンダラープロセスでの使用例は以下の通りです:

typescript// src/renderer/src/App.tsx
import React, { useState } from 'react';

function App(): JSX.Element {
  const [fileContent, setFileContent] =
    useState<string>('');

  const handleOpenFile = async (): Promise<void> => {
    try {
      const filePath = await window.electronAPI.openFile();
      if (filePath) {
        // ファイルの内容を読み込む処理
        console.log('Selected file:', filePath);
      }
    } catch (error) {
      console.error('File open error:', error);
    }
  };

  return (
    <div className='container'>
      <h1>Electron + Vite + TypeScript App</h1>
      <button onClick={handleOpenFile}>
        ファイルを開く
      </button>
      <textarea
        value={fileContent}
        onChange={(e) => setFileContent(e.target.value)}
        placeholder='ファイルの内容がここに表示されます'
      />
    </div>
  );
}

export default App;

動作確認

セットアップが完了したら、以下のコマンドで動作確認を行います:

bash# 依存関係のインストール
yarn install

# 開発サーバーの起動
yarn dev

# リンティングチェック
yarn lint

# ビルドテスト
yarn build

正常に動作すれば、以下の要素が確認できるはずです:

確認項目期待する結果
起動時間5 秒以内での起動
ホットリロードコード変更の即座反映
型チェックTypeScript エラーの検出
リンティングESLint ルールの適用
セキュリティContext Isolation の有効化

図で理解できる要点

  • Vite ベースのワークフローは設定が簡単で高速動作を実現
  • Preload アーキテクチャによりセキュリティと機能性を両立
  • TypeScript 統合により型安全な開発環境を構築

まとめ

最短ルートでの環境構築メリット

本記事でご紹介した Vite + TypeScript + ESLint + Preload 分離の組み合わせにより、従来の Electron アプリ開発における課題を大幅に解決できることが分かりました。

最も重要なメリットはセットアップ時間の劇的な短縮です。従来の Webpack ベースでは数時間から数日かかっていた環境構築が、わずか数分で完了するようになりました。この時間短縮により、開発者はより価値のあるアプリケーション機能の実装に集中できます。

また、セキュリティ面での改善も大きなポイントです。Preload スクリプトの適切な分離と Context Isolation の活用により、セキュリティリスクを最小限に抑えながら必要な機能を実現できます。これにより、エンタープライズレベルのアプリケーション開発にも安心して適用できるでしょう。

開発体験の向上も見逃せません。Vite の高速なホットリロード、TypeScript の型補完、ESLint によるリアルタイムエラー検出により、開発者のストレスが大幅に軽減されます。

今後の開発効率向上への道筋

このセットアップを基盤として、さらなる開発効率向上を図ることができます。今後検討すべき拡張項目をご紹介いたします。

テスト環境の整備が次のステップとして重要です。Jest、Playwright、または Cypress などのテストフレームワークを導入することで、品質の高いアプリケーションを継続的に開発できるようになります。

CI/CD パイプラインの構築も効果的です。GitHub Actions や GitLab CI を活用して、自動ビルド、テスト、デプロイの仕組みを構築することで、チーム開発における品質管理が格段に向上します。

パフォーマンス監視ツールの導入も考慮すべきでしょう。Electron アプリ特有のメモリ使用量や CPU 負荷を監視する仕組みを構築することで、ユーザー体験の向上につながります。

最後に、チーム開発における標準化も重要な要素です。今回ご紹介した設定をプロジェクトテンプレート化し、組織内での標準的な開発環境として展開することで、チーム全体の生産性向上が期待できます。

このように、Vite + TypeScript + ESLint + Preload 分離の組み合わせは、単なるセットアップの簡素化にとどまらず、長期的な開発効率向上への土台となります。ぜひこのアプローチを活用して、より良い Electron アプリ開発を実現していただければと思います。

関連リンク

公式ドキュメント

開発ツール

セキュリティガイド

コミュニティリソース