Per creare un'app desktop a partire da un'app Capacitor, avvolgi la stessa build web (webDir) in una shell di Electron: un processo principale che apre un BrowserWindow, uno script di caricamento che esporre un piccolo desktop API, e electron-builder per produrre installatori per macOS, Windows e Linux. La tua interfaccia utente e la logica di business rimangono condivise con iOS e Android, e le funzionalità desktop esclusivi vivono nel processo principale di Electron.
Questa guida copre lo stato attuale di Capacitor su Electron nel 2026, una configurazione sicura che funziona con Capacitor 8, il comportamento dei plugin, la confezione e la firma, e come distribuire aggiornamenti web senza nuovi installatori.
Lo stato attuale di Capacitor su Electron nel 2026
Per anni la risposta di default era @capacitor-community/electron, which plugged Electron into the Capacitor CLI as a platform. That project is now marked unmaintained in its own README. Its last npm release, 5.0.1, shipped in September 2023, requires Capacitor 5.4 or later, and was built against Electron 26. Electron has shipped many major versions since, and only the latest three majors receive security fixes.
Si può ancora utilizzarlo sui vecchi progetti, ma per un'app Capacitor 8 la comparazione assomiglia a questo:
| Approccio | Vantaggi | Vantaggi |
|---|---|---|
| La propria shell di Electron (questa guida) | Electron corrente, controllo completo, nessuna astrazione aggiuntiva | Si scrive circa 60 righe di code principale e preload |
@capacitor-community/electron |
Familiare cap comandi |
Non mantenuto, bloccato su vecchi Capacitor e Electron |
L'approccio shell è meno lavoro di quanto sembri, perché Capacitor produce già una costruzione web statica. Electron deve solo caricarla.
Come comportano i plugin in Electron
Il renderer di Electron è Chromium, quindi il tuo Capacitor app esegue lì come esegue in un browser. Capacitor's core non rileva nessun ponte nativo e ricade su ogni plugin web implementazione.
| Tipo di plugin | Funziona in Electron? | Cosa fare |
|---|---|---|
| Plugin con implementazione web (Preferenze, Condividi, Incolla, molti plugin Capgo) | Sì, utilizzando la versione web | Testa il comportamento web |
| Plugin nativo (nessuna implementazione web) | No, rifiuta come non implementato | Scrivi un equivalente di Electron dietro un preload API |
| Librerie JS puramente | Sì | Nulla |
Capacitor.getPlatform() ritorna 'web' in questa configurazione e Capacitor.isNativePlatform() ritorna false. Esponiamo un flag desktop esplicito dallo script preload affinché il tuo code possa diramarsi pulitamente.
Prerequisiti
- Un'app Capacitor che
bun run buildproduce una cartella statica (lawebDirIn generecapacitor.config.tso spessodistowww). - Bun e una versione corrente LTS di Node.js, poiché gli strumenti di Electron sono ancora basati su Node.
- Per la distribuzione su macOS, un certificato Apple Developer ID. Per Windows, un certificato di firma code.
Passo 1: Installare Electron e il pacchettizzatore
bun add -d electron electron-builder
Aggiungi un electron/ cartella nella radice del progetto:
my-app/
capacitor.config.ts
dist/ # web build, shared with iOS and Android
electron/
main.ts
preload.ts
electron-builder.yml
Passo 2: Carica la build web dal disco
Electron carica i file con il file:// protocollo in produzione. Due impostazioni sono importanti:
- Periferiche relative With Vite, impostare
base: './'così le riferenze costruiteindex.htmlriferimenti./assets/...al posto di/assets/.... - Routing. Deep link come
/settingsnon esistono come file. Utilizzare la routing hash per la build desktop, o assicurarsi che il router si muova solo in modo client-side dopo il caricamentoindex.html.
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
base: './',
});
Le percorrenze relative funzionano anche su iOS e Android, quindi non è necessario una build separata.
Passo 3: Scrivere il processo principale
// 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();
});
Conservare contextIsolation: true, nodeIntegration: false, e sandbox: true. La tua app web può caricare script di terze parti, e nessuno di loro dovrebbe avere accesso a Node.js.
Etappe 4: Espone un piccolo desktop API dal preload
// 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>,
});
Digita il comando nel tuo web code e utilizzalo allo stesso modo in cui utilizzi un plugin:
// src/desktop.ts
declare global {
interface Window {
desktop?: {
isElectron: boolean;
platform: string;
getVersion: () => Promise<string>;
};
}
}
export const isDesktop = () => Boolean(window.desktop?.isElectron);
Ogni funzionalità esclusiva nativa che hai bisogno sul desktop diventa una ipcMain.handle una nella main process e una funzione nel preload. Verifica gli argomenti nella main process, poiché il renderer è non affidabile.
Etappe 5: Costruisci e esegui
Compila i file Electron con Bun. Il preload viene eseguito in sandbox, quindi compila tutto come un singolo file 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"
}
}
Durante lo sviluppo, avvia il tuo server di sviluppo in un terminale (bun run dev) e bun run electron:dev in un altro. Otterrai il riavvio caldo nella finestra desktop.
Etappe 6: Pacchetta gli installatori con 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
Avvia bun run electron:pack.electron-builder costruisce per il sistema operativo su cui viene eseguito. Utilizza una matrice di esecutori macOS, Windows e Linux in CI per produrre tutti e tre.
| OS | Obiettivi comuni | Firma |
|---|---|---|
| macOS | .dmg, .zip |
Certificato di sviluppatore ID più notarizzazione, o Gatekeeper blocca l'app |
| Windows | NSIS .exe, MSI |
Code certificato di firma per evitare avvisi SmartScreen |
| Linux | AppImage, .deb, .rpm |
Facoltativo |
Elettroni app sono più grandi delle costruzioni mobili perché includono Chromium e Node.js. Si aspettano installatori di circa 80 a 150 MB a seconda dei target.
Passo 7: Invia aggiornamenti web senza nuovi installatori
La maggior parte delle modifiche in un'app Capacitor sono modifiche web. Su mobile puoi inviarle con Aggiornamenti Capgo in tempo reale. Su desktop, @capgo/electron-updater fa lo stesso lavoro con lo stesso modello: canali, un pacchetto integrato, pacchetti scaricati e rollback automatico se il nuovo pacchetto non chiama notifyAppReady().
bun add @capgo/electron-updater
Aggiorna il processo principale per consentire all'aggiornatore di scegliere il bundle da caricare:
// 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());
Aggiungi il ponte di aggiornamento alla stessa cartella preload:
// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';
exposeUpdaterAPI();
E conferma un lancio sano dall'app web, solo su desktop:
import { isDesktop } from './desktop';
if (isDesktop()) {
const { requireUpdater } = await import('@capgo/electron-updater/renderer');
await requireUpdater().notifyAppReady();
}
Carica un nuovo pacchetto con lo stesso CLI che usi per mobile:
bun run build
bunx @capgo/cli@latest bundle upload --channel=production
Gli aggiornamenti web non possono modificare l'Elettroni stesso, il processo principale o il caricamento predefinito. Per questi, invia un nuovo installatore con un aggiornatore binario auto. La nostra guida su Aggiornamenti Elettroni copre quella via, e il Plugin di aggiornamento per Electron e docs copre ogni opzione.
Troubleshooting
Vetrina vuota nell'applicazione pacchettizzata. Gli asset utilizzano percorsi assoluti. Imposta base: './' and rebuild. Open DevTools in the packaged app to confirm 404s on file:///assets/....
Le route mostrano una pagina vuota dopo il riavvio. Il router ha cercato di caricare /route da disco. Passa a routing con hash per desktop.
require is not defined o errori di caricamento predefiniti. Il caricamento predefinito sandbox non può caricare pacchetti arbitrari in esecuzione. Compilalo in un file unico, come nel script di compilazione sopra.
Una chiamata di plugin rifiuta con “non implementato su web”. Quel plugin non ha una implementazione web. Aggiungi un gestore IPC nel processo principale e chiamalo attraverso window.desktop.
macOS dice che l'app è danneggiata o proveniente da un sviluppatore non identificato. L'app non è firmata e non certificata. Configura un certificato di sviluppatore ID e la notarizzazione in electron-builder.
Updates roll back right after install. notifyAppReady() non è stato chiamato entro il timeout (10 secondi di default). Chiamalo presto all'avvio del tuo app.
Riassunto
Non hai bisogno di un pacchetto di piattaforma dedicato Capacitor per raggiungere il desktop. Un breve processo principale di Electron, un caricamento predefinito che esporre un API tipizzato, percorsi di asset relativi e electron-builder ti danno installatori per tutti e tre i sistemi operativi a partire dalla stessa build web. Aggiungi @capgo/electron-updater e l'app del desktop ottiene lo stesso workflow live update come le tue costruzioni iOS e Android. Se sei ancora scegliendo un runtime desktop, leggi il nostro Confronto tra Electron e Tauri.