Claude CodeでElectronアプリを開発する手順 — メイン/レンダラー構成の実装
Electron公式チュートリアルをもとに、Claude CodeでElectronアプリのメイン/レンダラープロセスを実装する手順とCLAUDE.mdの使い方をまとめました。
Electronのメイン/レンダラープロセスとは
Electronは、Node.jsとChromiumを組み合わせてデスクトップアプリを作るフレームワークです。アプリは必ず2つのプロセスに分かれます。package.jsonのmainフィールドが指すスクリプトを実行するメインプロセスと、各ウィンドウが表示するWebページを動かすレンダラープロセスです。メインプロセスはNode.js環境で動き、アプリのライフサイクル管理やネイティブなウィンドウ操作を担当します。レンダラープロセスはChromiumのタブに相当し、通常のWeb開発と同じAPIとツールが使えます。Claude Codeでこの構成を組むときは、2つのプロセスの役割分担を先にCLAUDE.mdへ書いておくと、生成されるコードがどちらのプロセスに属するかで迷いにくくなります。
前提条件とプロジェクトの準備
Electron公式チュートリアルは、WindowsでWSL(Windows Subsystem for Linux)を使うとアプリの実行時に問題が起きるため避けるよう案内しています。Claude Codeをネイティブインストールで使う場合も、同じ前提で進めます。
Claude Codeはターミナル・VS Code拡張・デスクトップアプリ・Webのいずれからも同じエンジンで動きます。デスクトップアプリでの動作確認はClaude Code Desktopアプリのプレビュー機能が参考になります。今回はターミナルでの利用を前提に進めます。未インストールなら、次のコマンドでセットアップします。
curl -fsSL https://claude.ai/install.sh | bashプロジェクトのフォルダを作り、npmパッケージとして初期化します。エントリーポイントはmain.jsにしておくのがElectronの慣例です。
mkdir my-electron-app && cd my-electron-app
npm init
npm install electron --save-devElectronは開発時のみ必要なdevDependencyとして入れます。実行に使うバイナリはElectronのパッケージングステップがまとめて処理するため、本番の依存関係には含めません。Yarn BerryやpnpmでnodeLinkerを標準以外の設定にしている場合は、node_modulesを物理的にディスクへ置く設定(node-modulesまたはhoisted)に変更しておく必要があります。パッケージング時にnode_modulesが実体として存在しないと、Electronのビルドが失敗するためです。
初期化のタイミングで.gitignoreも置いておきます。GitHubが配布しているNode.js向けの.gitignoreテンプレートをプロジェクトルートにコピーすれば、node_modulesフォルダをコミット対象から外せます。Claude Codeにコミットを依頼する前にこのファイルを用意しておくと、生成される差分に依存パッケージ一式が紛れ込みません。
ステップ1: CLAUDE.mdでプロジェクトの前提を固める
Claude Codeはセッションのたびに新しいコンテキストで起動します。プロジェクトの前提を毎回説明し直さずに済ませる仕組みがCLAUDE.mdです。プロジェクトルートにCLAUDE.md(または.claude/CLAUDE.md)を置くと、セッション開始時に自動で読み込まれます。
既存コードが無い新規プロジェクトでも、/initコマンドを使うとClaude Codeがコードベースを解析し、ビルドコマンドやテスト手順、発見した規約を含む下書きを自動生成します。実装前に仕様を明文化してからコードを書かせたい場合は、cc-sddによる仕様駆動開発の手順も選択肢になります。Electronプロジェクトでは、/initの下書きに次のような前提を書き足しておくと効果的です。
- エントリーポイントは
main.jsで、メインプロセスのコードだけを置く - レンダラー側のコードは
index.htmlと付随するJS/CSSに分離する npm run startでelectron .が実行され、開発モードで動作確認できる
ステップ2: メインプロセスを書く(main.js)
メインプロセスの動作確認は、まず1行のログ出力から始めます。ルートにmain.jsを作り、次のコードを置きます。
console.log('Hello from Electron 👋')package.jsonのscriptsフィールドにelectron .を実行するstartスクリプトを追加します。
npm pkg set scripts.start="electron ."
npm run startターミナルにHello from Electron 👋と表示されれば、メインプロセスの起動は成功です。次に、ウィンドウを開く実装に進みます。appモジュールでアプリのライフサイクルを、BrowserWindowモジュールでウィンドウの生成・管理を行います。
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})BrowserWindowはウィンドウが作られる前には呼び出せません。app.whenReady()が返すPromiseが解決してからcreateWindow()を呼ぶのはこのためです。Electronのモジュールは命名規則にも意味があります。BrowserWindowのようにPascalCaseのモジュールはインスタンス化できるクラスで、appのようにcamelCaseのモジュールはインスタンス化しません。
ステップ3: レンダラープロセスにWebページを読み込む
レンダラー側は通常のHTMLファイルとして書けます。index.htmlをルートに作成します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>win.loadFile('index.html')でこのファイルをBrowserWindowに読み込むと、レンダラープロセスとして起動します。レンダラープロセスはウィンドウごとに独立したプロセスで動き、通常のフロントエンド開発と同じAPIやツールチェーンが使えます。webpackでバンドルしたりReactでUIを組んだりする構成も、そのまま持ち込めます。IPCを使ったメイン/レンダラー間の通信やpreloadスクリプトは、この最小構成の次のステップにあたります。ここではまず、2プロセスが独立して起動する構成を固めることを優先します。
ステップ4: ウィンドウのライフサイクルをOSごとに実装する
ウィンドウの閉じ方・開き方はOSごとに慣習が異なります。Electronはこれをデフォルトで強制しないため、アプリ側でイベントを拾って実装します。
| イベント | 対象OS | 挙動 |
|---|---|---|
window-all-closed | 対象OSWindows / Linux | 挙動全ウィンドウが閉じたらアプリごと終了する |
activate | 対象OSmacOS | 挙動ウィンドウが0個の状態でアプリがアクティブ化されたら新規作成する |
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})activateイベントはウィンドウが作られる前には発火しないため、whenReady()のコールバック内でのみ登録します。process.platformの判定に使える値は、Windowsがwin32、Linuxがlinux、macOSがdarwinの3種類です。
補足: VS Codeでメイン/レンダラー両方をデバッグする
メインプロセスとレンダラープロセスは別プロセスなので、両方を同時にデバッグするには別々のアタッチ方法が要ります。.vscode/launch.jsonに次の設定を追加します。
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}「Main」はNode.jsプロセスとしてメインプロセスを起動し、リモートデバッグ用に9222番ポートを開きます。「Renderer」はそのポートへChromeデバッガとしてアタッチします。「Main + renderer」は両方を同時に起動する複合タスクです。サイドバーの「実行とデバッグ」から「Main + renderer」を選ぶと、両プロセスに同時にブレークポイントを置けます。
レンダラー側はアタッチ方式のため、デバッガの接続が間に合わず起動直後の数行が飛ばされることがあります。気づいたときは、ページをリフレッシュするか、コードの先頭で一瞬待ってから処理を始めると回避できます。
Claude Codeでの実装を使い分ける早見表
Electronアプリの実装作業は、プロセスの種類やタスクの性質によってClaude Codeへの頼み方を変えると進めやすくなります。
| 作業内容 | 向いている進め方 | 理由 |
|---|---|---|
| main.js/package.jsonの初期実装 | 向いている進め方ターミナルでプロンプト実行 | 理由定型のボイラープレートで、生成速度を優先しやすい |
| ウィンドウのUI調整(index.html) | 向いている進め方VS Code拡張でインラインdiff確認 | 理由レンダラー側の見た目はコード差分より画面で確認したい |
| ライフサイクルイベントの実装 | 向いている進め方CLAUDE.mdに規約を明記してから依頼 | 理由OS分岐のロジックはプロジェクト固有の前提が要る |
| デバッグ設定(launch.json) | 向いている進め方ターミナルで直接生成 | 理由定型のJSON構造で、Electron公式の設定例をそのまま流用できる |
よくあるつまずき
- WSL環境での実行エラー: WindowsでWSLを使うと、ビルドや実行時にエラーが出ることがあります。ネイティブのWindows環境かmacOS/Linuxで進めます。
- node_modulesが実体として無い: Yarn BerryやpnpmでnodeLinkerが標準以外の設定だと、パッケージング時にElectronのバイナリが見つからず失敗します。
node-modulesまたはhoistedに設定を変更します。 - BrowserWindowを
whenReady()より前に呼ぶ: ウィンドウはアプリのready前には作成できません。activateイベントの登録もwhenReady()のコールバック内に置きます。 - CLAUDE.mdにプロセスの役割分担を書かない: メイン/レンダラーの区別をCLAUDE.mdに書かずに実装を依頼すると、レンダラー側のコードがmain.jsに混ざることがあります。先に前提を明文化しておくと迷いが減ります。
まとめ
Electronアプリは、package.jsonのmainフィールドが指すメインプロセスと、BrowserWindowが読み込むレンダラープロセスの2つで構成されます。Claude Codeでこの構成を組むときは、/initで下書きしたCLAUDE.mdにプロセスの役割分担とnpm run startでの動作確認方法を明記しておくと、以降の実装依頼がぶれにくくなります。Claude Codeを使った開発ツール導入の実例は導入事例のまとめで紹介しています。最小構成が動いたら、次はpreloadスクリプトを使ったメイン/レンダラー間の通信の実装に進みます。