Claude Media
Claude CodeでElectronアプリを開発する手順 — メイン/レンダラー構成の実装

Claude CodeでElectronアプリを開発する手順 — メイン/レンダラー構成の実装

Electron公式チュートリアルをもとに、Claude CodeでElectronアプリのメイン/レンダラープロセスを実装する手順とCLAUDE.mdの使い方をまとめました。

Electronアプリは2つのプロセスに分かれる

Electronは、Node.jsとChromiumを組み合わせてデスクトップアプリを作るフレームワークです。実行すると、必ず次の2種類のプロセスに分かれます。Claude Codeに実装を頼むときは、この線引きが指示の土台になります。

くらべる

メインとレンダラーの役割

Node.js

メインプロセス

package.jsonのmainフィールドが指すスクリプトを実行します。アプリのライフサイクル、ネイティブなウィンドウ操作、特権が必要な処理を担当します。requireでモジュールを読み込め、Node.jsのAPIがすべて使えます。

Chromium

レンダラープロセス

ウィンドウ(BrowserWindow)ごとに1つ起動し、Webページを描画します。コードはWeb標準に沿って動くため、requireやNode.jsのAPIは直接使えません。npmモジュールを使うには、webpackやparcelのようなバンドラーを通します。

レンダラーでNode.jsを直接使える構成は、かつての既定でした。現在はセキュリティ上の理由で無効にされています。メインの機能をレンダラーから呼ぶ橋渡しはpreloadスクリプトの役目で、この記事の最小構成にはまだ含めません。

Windowsで進める前にWSLを外す

Electron公式チュートリアルは、WindowsではWSL(Windows Subsystem for Linux)を使わないよう案内しています。アプリを実行する段階で問題が起きるためです。

Claude Codeのインストールは、macOS / Linuxなら次のコマンドです。Windowsのネイティブ環境はPowerShellでirm https://claude.ai/install.ps1 | iexを実行します。公式のインストール手順では、終了後に新しいターミナルを開いてclaude --versionを実行し、バージョンが表示されれば成功です。

curl -fsSL https://claude.ai/install.sh | bash
claude --version

v2.1.289(macOS)では、claude --versionが2.1.289 (Claude Code)と表示されます。Claude Codeの全体像はClaude Codeとはにまとめています。Claude Code Desktopアプリで動作を見たい場合は、Desktopアプリのプレビュー機能が参考になります。

依頼の区切り方: 起動確認を挟みながら4段階で進める

Electronは「動かして初めて分かる」箇所が多いので、一度に全部を生成させず、起動できる状態を保ちながら区切ります。

手順

Claude Codeへの依頼の区切り

  1. 1

    CLAUDE.mdを置く

    プロセスの役割と起動コマンドを書き、/contextで読み込みを確かめます。

  2. 2

    main.jsに1行だけ書く

    npm run startでログが出れば、entry pointとstartスクリプトが正しいと分かります。

  3. 3

    BrowserWindowとindex.htmlを足す

    ウィンドウが開き、見出しが表示されることを目で確かめます。

  4. 4

    OS別のウィンドウ挙動を足す

    最後にwindow-all-closedとactivateを入れ、閉じる・開くを試します。

プロジェクトの土台を作る

プロジェクトのフォルダを作り、npmパッケージとして初期化します。entry pointはmain.jsにしておくのがElectron公式チュートリアルの前提です。author・license・descriptionは任意の値で構いませんが、後でパッケージングするときに必要になります。

mkdir my-electron-app && cd my-electron-app
npm init
npm install electron --save-dev

Electronは開発時のみ必要なdevDependencyとして入れます。実行用のバイナリはパッケージングの段階でまとめて処理されるため、本番の依存関係には含めません。

Yarn Berryやpnpmを使う場合は、設定を1つ変えます。Electronのパッケージングはnode_modulesが物理的にディスク上にあることを前提とするためです。YarnではnodeLinker: node-modules、pnpmではnodeLinker: hoistedを設定します。

初期化の時点で.gitignoreも置きます。GitHubが配布しているNode.js向けのテンプレートをプロジェクトルートへコピーすれば、node_modulesがコミット対象から外れます。Claude Codeにコミットを任せる前に用意しておくと、差分に依存パッケージが紛れ込みません。

CLAUDE.mdに2つのプロセスの前提を書く

Claude Codeはセッションのたびに新しいコンテキストで始まります。毎回の説明を省く仕組みがCLAUDE.mdです。プロジェクトルートのCLAUDE.mdか.claude/CLAUDE.mdに置くと、起動時に読み込まれます。

/initを実行すると、Claude Codeがコードベースを調べ、ビルドコマンドや規約を含む下書きを作ります。すでにCLAUDE.mdがある場合は、上書きせず改善案を出します。環境変数CLAUDE_CODE_NEW_INITを1にしておくと(シェルでも、設定ファイルのenvブロックでも指定できます)、/initはCLAUDE.md・skills・hooksのどれを作るかを尋ねる対話形式に変わります。

下書きができたら、設計判断を手で書き足します。Electronなら、次の内容が候補です。

## プロセスの分担
- main.js はメインプロセス専用。ウィンドウ生成とアプリのライフサイクルだけを書く
- index.html とその JS/CSS はレンダラー専用。require は使わない
- メインの機能をレンダラーから呼ぶときは preload スクリプトを経由する
 
## コマンド
- `npm run start` で `electron .` が走る。動作確認は必ずこのコマンドで行う

書いたあとは、セッション内で/contextを実行します。公式ドキュメントでは、Memory filesの一覧にファイルが並んでいれば読み込めている状態です。長さは1ファイル200行未満が目安です。長くなると、コンテキストを消費する割に指示が守られにくくなります。実装前に仕様を文章にしておきたい場合は、cc-sddによる仕様駆動開発の手順も選択肢になります。

メインプロセスを書いて起動を確かめる

まず、ルートにmain.jsを作り、1行だけ書きます。

console.log('Hello from Electron 👋')

package.jsonのscriptsにstartを足して実行します。

npm pkg set scripts.start="electron ."
npm run start

ターミナルにHello from Electron 👋と出れば、メインプロセスの起動は成功です。Electronのメインプロセスは通常のNode.jsランタイムなので、electronコマンドで任意のNode.jsコードを実行することもできます。

次に、ウィンドウを開く実装へ進みます。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のreadyイベントより前には作れません。app.whenReady()のPromiseが解決してからcreateWindow()を呼ぶのはこのためです。モジュール名の大文字小文字にも意味があります。PascalCaseのBrowserWindowは、インスタンス化できるクラスです。camelCaseのappは、インスタンス化しません。

公式の本文とサンプルで違う書き方

Electron公式チュートリアルの本文と、末尾の完成サンプル(Fiddle 44.5.1)では、同じ処理の書き方が少し違います。Claude Codeに生成させたコードと見比べるときに、差として目に付く箇所です。

項目本文のコード完成サンプル
モジュールの読み込み本文のコードrequire('electron')完成サンプルrequire('electron/main')
CSPのmeta本文のコードContent-Security-Policyのみ完成サンプルX-Content-Security-Policyも併記

electron/mainは、TypeScriptで型チェックを効かせたいときに選べる別名です。公式の補足では、メインプロセスのモジュールをこの名前で読み込めるとされています。どちらの書き方でも動くので、プロジェクトで1つに決めてCLAUDE.mdに書いておくと、生成のたびに表記が揺れません。ESモジュール(import)はElectron 28以降で使えます。

公式ページのpackage.json例には"electron": "23.1.3"と書かれています。これは記載例の番号で、npm install electron --save-devを実行した時点のバージョンが実際には書き込まれます。

レンダラープロセスに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')でこのファイルを読み込むと、レンダラープロセスとして起動します。CSPのmetaは、読み込めるリソースを自分のオリジンに限る設定です。Claude Codeがスクリプトを外部URLから読む構成を提案してきたときは、この設定で止まります。

webpackでバンドルしたり、Reactで画面を組んだりする構成も、そのまま持ち込めます。メイン/レンダラー間の通信は、次のステップであるpreloadスクリプトの領分です。

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つです。

npmコマンドの実行許可を絞る

Electronの開発では、npm run startのようなコマンドを何度も流します。毎回の確認を減らしたいときは、プロジェクトの設定で許可ルールを書けます。公式ドキュメントの例を、この構成に合わせると次のとおりです。

{
  "permissions": {
    "allow": [
      "Bash(npm run *)"
    ],
    "deny": [
      "Bash(npm publish *)"
    ]
  }
}

Bash(npm run *)はnpm run startやnpm run buildに一致し、npm installには一致しません。*の前の空白もルールの一部です。Bash(npm run*)と書くと、空白が無い分だけ一致する範囲が広がります。

起動時の動作は--permission-modeで選べます。値にはdefault(別名manual)・acceptEdits・auto・bypassPermissions・dontAsk・planを指定できます。実装前にコードベースだけ読ませたい場合は、claude --permission-mode planで始めると、プランを承認するまで編集がブロックされます。

VS Codeでメイン/レンダラーを同時にデバッグする

2つのプロセスは別々にアタッチします。.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",
      "windows": {
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
      }
    }
  ]
}

「Main」はNode.jsプロセスとしてメインを起動し、9222番ポートを開きます。Windowsではwindowsブロックの指定により、electron.cmdが実行されます。「Renderer」はそのポートへChromeデバッガとしてアタッチします。サイドバーの「実行とデバッグ」から「Main + renderer」を選ぶと、両方にブレークポイントを置けます。

レンダラー側はアタッチ方式です。そのため、デバッガの接続が間に合わず、起動直後の数行が飛ぶことがあります。ページをリフレッシュするか、コードの先頭で一瞬待つと避けられます。

よくあるつまずき

つまずき

動かないときの4つの原因

  • WSLで実行している

    アプリの実行段階でエラーが出ます。Windows側のターミナルかmacOS / Linuxでnpm run startを流します。

  • node_modulesが実体で無い

    Yarn Berryやpnpmの既定の方式だと、パッケージングで失敗します。nodeLinkerをnode-modulesまたはhoistedにします。

  • readyより前にウィンドウを作った

    BrowserWindowはreadyの後にしか作れません。activateの登録もwhenReady()の中に置きます。

  • 役割分担を書かずに依頼した

    メインとレンダラーの区別がCLAUDE.mdに無いと、コードの置き場所がその場の依頼文だけで決まります。先に分担を書いておくと、置き場所の基準が揃います。

まとめ

最小構成の骨格は、メインのmain.jsとレンダラーのindex.htmlの2ファイルです。Claude Codeに任せる範囲は、CLAUDE.mdで2つのプロセスの分担と起動コマンドを固定してから広げると、生成物のぶれが小さくなります。Claude Codeを使った開発ツール導入の実例は導入事例のまとめにあります。次はpreloadスクリプトでメインとレンダラーをつなぐ段階です。

この記事を共有:XはてブLinkedIn