Capacitor アプリからデスクトップアプリを構築するには、同じ Web ビルド (webDir) を Electron シェルにラップします。ウィンドウを開くメインプロセスが BrowserWindow, 小さなデスクトップ用 API を公開する preload スクリプト、そして macOS、Windows、Linux 用のインストーラーを生成する electron-builder を使います。UI とビジネスロジックは iOS や Android と共通のまま使え、デスクトップ専用の機能は Electron のメインプロセスに置きます。
このガイドでは、2026 年時点の Electron 上の Capacitor の現状、Capacitor 8 で動くセキュアなセットアップ、プラグインの挙動、パッケージングと署名、そして新しいインストーラーなしで Web アップデートを配信する方法を解説します。
2026 年時点の Electron 上の Capacitor の現状
長年、定番の答えは @capacitor-community/electron, でした。これは Electron をプラットフォームとして Capacitor CLI に接続するものでした。しかし、そのプロジェクトは自身の README でメンテナンスされていないと明記されています。最後の npm リリースとなる 5.0.1 は 2023 年 9 月公開で、Capacitor 5.4 以降を必要とし、Electron 26 を対象にビルドされています。それ以降 Electron は多くのメジャーバージョンをリリースしており、セキュリティ修正が入るのは最新 3 メジャーのみです。
古いプロジェクトでは引き続き使えますが、Capacitor 8 のアプリでは比較するとこうなります:
| アプローチ | メリット | デメリット |
|---|---|---|
| 自前の Electron シェル(本ガイド) | 最新の Electron、完全なコントロール、余計な抽象化なし | メインと preload の code を約 60 行書くだけ |
@capacitor-community/electron |
使い慣れた cap コマンド |
メンテナンスされておらず、古い Capacitor と Electron に固定されている |
シェル方式は、Capacitor がすでに静的な Web ビルドを生成しているため、思ったより手間がかかりません。Electron はそれをロードするだけで済みます。
Electron でのプラグインの動作
Electron のレンダラーは Chromium なので、Capacitor アプリはブラウザと同じように動作します。Capacitor のコアはネイティブブリッジを検知せず、各プラグインの Web 実装にフォールバックします。
| プラグインの種類 | Electron で動作する? | 対処方法 |
|---|---|---|
| Web 実装を持つプラグイン(Preferences、Share、Clipboard、多くの Capgo プラグイン) | はい、Web 版を使用します | Web での動作をテストする |
| ネイティブ専用プラグイン(Web実装なし) | いいえ、未実装として拒否されます | preload API の背後にElectron相当のコードを書く |
| 純粋なJSライブラリ | はい | なし |
Capacitor.getPlatform() を返します 'web' このセットアップでは、 Capacitor.isNativePlatform() を返します false。preload スクリプトから明示的なデスクトップフラグを公開し、code がクリーンに分岐できるようにします。
前提条件
- 静的フォルダを出力する Capacitor アプリの
bun run build(webDirにおけるcapacitor.config.ts、多くの場合distまたはwww). - Bun と最新の LTS 版 Node.js。Electron ツールは依然として Node で動作するためです。
- macOS の配布には Apple Developer ID 証明書、Windows には code 署名証明書が必要です。
ステップ 1: Electron と packager をインストールする
bun add -d electron electron-builder
プロジェクトのルートに electron/ フォルダを追加します:
my-app/
capacitor.config.ts
dist/ # web build, shared with iOS and Android
electron/
main.ts
preload.ts
electron-builder.yml
ステップ 2: Web ビルドがディスクから読み込まれるようにする
Electron は本番環境で file:// プロトコルを使ってファイルを読み込みます。2つの設定が重要です:
- アセットの相対パス。 Vite を使用する場合、
base: './'を設定し、ビルドされたindex.htmlが./assets/...の代わりに/assets/.... - ルーティング。 ディープリンクなど
/settingsファイルとして存在しません。デスクトップビルドではハッシュルーティングを使用するか、読み込み後にクライアントサイドでのみナビゲーションを行うようルーターを設定してください。index.html.
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
base: './',
});
相対パスは iOS と Android でも動作するため、別のビルドは不要です。
ステップ 3: メインプロセスを書く
// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import path from 'node:path';
const isDev = !app.isPackaged;
async function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
win.once('ready-to-show', () => win.show());
// Open external links in the default browser, never inside the app.
// Only allow https: so a script cannot launch file: or other protocol handlers.
win.webContents.setWindowOpenHandler(({ url }) => {
try {
if (new URL(url).protocol === 'https:') void shell.openExternal(url).catch(() => {});
} catch {
// invalid URL: ignore
}
return { action: 'deny' };
});
if (isDev) {
await win.loadURL('http://localhost:5173');
win.webContents.openDevTools({ mode: 'detach' });
} else {
await win.loadFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
}
}
ipcMain.handle('desktop:get-version', () => app.getVersion());
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
保持 contextIsolation: true, nodeIntegration: falseや sandbox: true。Web アプリはサードパーティのスクリプトを読み込む可能性がありますが、それらに Node.js へのアクセスを許可すべきではありません。
ステップ 4: preload から小さなデスクトップ API を公開
// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('desktop', {
isElectron: true,
platform: process.platform,
getVersion: () => ipcRenderer.invoke('desktop:get-version') as Promise<string>,
});
Web code に記述し、プラグインと同様に使用します:
// src/desktop.ts
declare global {
interface Window {
desktop?: {
isElectron: boolean;
platform: string;
getVersion: () => Promise<string>;
};
}
}
export const isDesktop = () => Boolean(window.desktop?.isElectron);
デスクトップで必要なネイティブ機能はそれぞれ 1 つの ipcMain.handle メインプロセスと、preload 内の 1 つの関数になります。レンダラーは信頼できないため、引数の検証はメインプロセスで行ってください。
ステップ 5: ビルドして実行
Electron ファイルを Bun でコンパイルします。preload はサンドボックス内で実行されるため、単一の CommonJS ファイルとしてバンドルしてください:
{
"main": "electron/dist/main.js",
"scripts": {
"build": "vite build",
"electron:compile": "bun build electron/main.ts --outdir electron/dist --target node --format cjs --external electron && bun build electron/preload.ts --outdir electron/dist --target node --format cjs --external electron",
"electron:dev": "bun run electron:compile && electron .",
"electron:pack": "bun run build && bun run electron:compile && electron-builder"
}
}
開発中は、1つのターミナルで開発サーバーを起動します(bun run dev)、離れるときは bun run electron:dev 別のターミナルで実行。デスクトップウィンドウでホットリロードが使えます。
ステップ 6: electron-builder でインストーラーをパッケージ化
# electron-builder.yml
appId: com.example.myapp
productName: My App
directories:
output: release
files:
- dist/**/*
- electron/dist/**/*
- package.json
mac:
target: [dmg, zip]
category: public.app-category.productivity
hardenedRuntime: true
win:
target: [nsis]
linux:
target: [AppImage, deb]
category: Utility
実行 bun run electron:packelectron-builder は実行している OS 向けにビルドします。CI で macOS、Windows、Linux のランナーをマトリックスで回して、3 つすべてを生成しましょう。
| OS | 主なターゲット | 署名 |
|---|---|---|
| macOS | .dmg, .zip |
Developer ID 証明書と公証がないと、Gatekeeper にアプリをブロックされます |
| Windows | NSIS .exe、MSI |
SmartScreen の警告を回避するには Code 署名証明書を使用してください |
| Linux | AppImage、 .deb, .rpm |
任意 |
Electron アプリは Chromium と Node.js を同梱するため、モバイルビルドよりもサイズが大きくなります。ターゲットによって異なりますが、インストーラーは 80〜150 MB 程度を想定してください。
ステップ 7: 新しいインストーラーなしでWebアップデートを配信
Capacitor アプリの変更の多くは Web 側の変更です。モバイルではこれらを Capgo Live Updatesで配信できます。デスクトップでは、 @capgo/electron-updater が同じモデル(チャネル、組み込みバンドル、ダウンロード済みバンドル、新バンドルが呼び出さない場合の自動ロールバック)で同じ役割を果たします。 notifyAppReady().
bun add @capgo/electron-updater
メインプロセスを更新し、アップデータが読み込むバンドルを選択できるようにします:
// electron/main.ts (production branch)
import { ElectronUpdater, setupIPCHandlers, setupEventForwarding } from '@capgo/electron-updater';
const updater = new ElectronUpdater({
appId: 'com.example.myapp',
autoUpdate: true,
});
// inside createWindow(), instead of loading dist/index.html directly
const builtinPath = path.join(__dirname, '..', '..', 'dist', 'index.html');
await updater.initialize(win, builtinPath);
setupIPCHandlers(updater);
setupEventForwarding(updater, win);
await win.loadFile(updater.getCurrentBundlePath());
同じ preload ファイルにアップデータブリッジを追加します:
// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';
exposeUpdaterAPI();
デスクトップ環境でのみ、Web アプリからの正常な起動を確認します:
import { isDesktop } from './desktop';
if (isDesktop()) {
const { requireUpdater } = await import('@capgo/electron-updater/renderer');
await requireUpdater().notifyAppReady();
}
モバイルで使用するのと同じ CLI で新しいバンドルをアップロードします:
bun run build
bunx @capgo/cli@latest bundle upload --channel=production
Web 更新では Electron 本体、メインプロセス、preload は変更できません。それらには、バイナリ自動更新機能付きの新しいインストーラーを配布してください。当社のガイドでは Electronの自動アップデート そのパスをカバーしており、 electron-updater プラグインのページ と ドキュメント すべてのオプションを網羅しています。
トラブルシューティング
パッケージ化されたアプリでウィンドウが真っ白になる。 アセットが絶対パスを使用しています。設定して base: './' 再ビルドしてください。パッケージ化されたアプリで DevTools を開き、404 を確認します。 file:///assets/....
リロード後にルートが空白ページになる。 ルーターが読み込もうとしました /route ディスクから。デスクトップ向けにはハッシュルーティングに切り替えてください。
require is not defined またはプリロードエラー。 サンドボックス化されたプリロードは、実行時に任意のパッケージをロードできません。上記のコンパイルスクリプトのように、1つのファイルにバンドルしてください。
プラグインの呼び出しが「not implemented on web」で拒否される。 そのプラグインにはWeb実装がありません。メインプロセスにIPCハンドラーを追加し、それを経由して呼び出してください。 window.desktop.
macOSで「アプリが破損しているか、未確認の開発元です」と表示される。 アプリが署名・公証されていません。electron-builderでDeveloper ID証明書と公証(notarization)を設定してください。
インストール直後にアップデートがロールバックされる。 notifyAppReady() タイムアウト(デフォルトは10秒)内に呼び出されませんでした。アプリ起動時に早めに呼び出してください。
まとめ
デスクトップ展開に専用の Capacitor プラットフォームパッケージは不要です。短いElectronメインプロセス、型付きの API を公開するプリロード、相対アセットパス、そしてelectron-builderがあれば、同じWebビルドから3つのOSすべてのインストーラーを作成できます。さらに、 @capgo/electron-updater デスクトップアプリでも、iOSやAndroidのビルドと同じ live update ワークフローが使えます。デスクトップランタイムを検討中の方は、こちらもご覧ください。 Electron vs Tauri の比較.