デスクトップアプリ開発を加速させる:Electron、React、TypeScriptガイド

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

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

すぐにウィンドウが表示されるはずです。フォルダ構成(mainrendererpreload)に注目してください。これは単なる整理整頓ではなく、セキュリティ上の要件です。これらのレイヤーを分離しておくことで、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通信をマスターすれば、すぐにプロフェッショナルなソフトウェアをリリースできるようになるでしょう。

Share: