Pour compiler une application desktop à partir d'une application Capacitor, utilisez le même build web (webDir) dans un shell Electron : un processus principal qui ouvre une BrowserWindow, un script de preload qui expose un petit API desktop, et electron-builder pour générer des installateurs macOS, Windows et Linux. Votre UI et votre logique métier restent partagées avec iOS et Android, tandis que les fonctionnalités spécifiques au desktop résident dans le processus principal Electron.
Ce guide couvre l'état actuel de Capacitor sur Electron en 2026 : une configuration sécurisée compatible avec Capacitor 8, le comportement des plugins, le packaging et la signature, ainsi que la manière de déployer des mises à jour web sans nouveaux installateurs.
L'état de Capacitor sur Electron en 2026
Pendant des années, la réponse par défaut était @capacitor-community/electron, qui connectait Electron au Capacitor CLI en tant que plateforme. Ce projet est désormais marqué comme non maintenu dans son propre README. Sa dernière release npm, la 5.0.1, est sortie en septembre 2023, nécessite Capacitor 5.4 ou plus, et a été compilée pour Electron 26. Electron a publié de nombreuses versions majeures depuis, et seules les trois dernières reçoivent des correctifs de sécurité.
Vous pouvez encore l'utiliser sur d'anciens projets, mais pour une application Capacitor 8, la comparaison est la suivante :
| Approche | Avantages | Cons |
|---|---|---|
| Votre propre shell Electron (ce guide) | Electron actuel, contrôle total, aucune abstraction supplémentaire | Vous écrivez environ 60 lignes de code pour le main et le preload |
@capacitor-community/electron |
Familier cap commandes |
Non maintenu, figé sur d’anciennes versions de Capacitor et d’Electron |
L’approche shell demande moins de travail qu’il n’y paraît, car Capacitor génère déjà un build web statique. Electron n’a plus qu’à le charger.
Comportement des plugins dans Electron
Le renderer d’Electron est Chromium : votre app Capacitor s’y exécute comme dans un navigateur. Le cœur de Capacitor ne détecte aucun bridge natif et bascule sur l’implémentation web de chaque plugin.
| Type de plugin | Fonctionne dans Electron ? | Que faire |
|---|---|---|
| Plugin avec une implémentation web (Preferences, Share, Clipboard, de nombreux plugins Capgo) | Oui, en utilisant la version web | Tester le comportement web |
| Plugin natif uniquement (pas d'implémentation web) | Non, rejeté comme non implémenté | Écrire un équivalent Electron derrière un preload API |
| Bibliothèques JS pures | Oui | Rien |
Capacitor.getPlatform() renvoie 'web' dans cette configuration et Capacitor.isNativePlatform() renvoie false. Nous exposerons un flag desktop explicite depuis le script preload pour que votre code puisse brancher proprement.
Prerequisites
- Une application Capacitor dont
bun run buildgénère un dossier statique (lewebDirdanscapacitor.config.ts, souventdistouwww). - Bun et une version LTS actuelle de Node.js, car les outils Electron fonctionnent toujours sous Node.
- Pour la distribution macOS, un certificat Apple Developer ID. Pour Windows, un certificat de signature code.
Étape 1 : Installer Electron et le packager
bun add -d electron electron-builder
Ajoutez un electron/ dossier à la racine du projet :
my-app/
capacitor.config.ts
dist/ # web build, shared with iOS and Android
electron/
main.ts
preload.ts
electron-builder.yml
Étape 2 : Faire charger le build web depuis le disque
Electron charge les fichiers avec le file:// protocole en production. Deux paramètres sont importants :
- Chemins d'assets relatifs. Avec Vite, configurez
base: './'pour que le buildindex.htmlréférences./assets/...au lieu de/assets/.... - Routage. Les deep links comme
/settingsn'existent pas en tant que fichiers. Utilisez le hash routing pour le build desktop, ou assurez-vous que votre routeur ne navigue qu'en client-side après le chargementindex.html.
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
base: './',
});
Les chemins relatifs fonctionnent aussi sur iOS et Android, vous n'avez donc pas besoin d'un build séparé.
Étape 3 : Écrire le processus principal
// 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();
});
Conservez contextIsolation: true, nodeIntegration: false, et sandbox: true. Votre application web peut charger des scripts tiers, et aucun d'entre eux ne doit avoir accès à Node.js.
Étape 4 : Exposer une petite API desktop depuis le 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>,
});
Saisissez-le dans votre code web et utilisez-le de la même manière qu'un plugin :
// src/desktop.ts
declare global {
interface Window {
desktop?: {
isElectron: boolean;
platform: string;
getVersion: () => Promise<string>;
};
}
}
export const isDesktop = () => Boolean(window.desktop?.isElectron);
Chaque fonctionnalité native-only dont vous avez besoin sur desktop devient une ipcMain.handle dans le processus principal et une fonction dans le preload. Validez les arguments dans le processus principal, car le renderer n'est pas fiable.
Étape 5 : Compiler et exécuter
Compilez les fichiers Electron avec Bun. Le preload s'exécute en sandbox, donc bundlez-l’en un seul fichier 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"
}
}
Pendant le développement, lancez votre serveur de dev dans un terminal (bun run dev) bun run electron:dev dans un autre. Vous bénéficiez du hot reload dans la fenêtre desktop.
Étape 6 : Générer les installateurs avec 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
Exécutez bun run electron:pack. electron-builder compile pour l'OS sur lequel il est exécuté. Utilisez une matrice de runners macOS, Windows et Linux en CI pour produire les trois.
| OS | Cibles courantes | Signature |
|---|---|---|
| machine macOS | .dmg, .zip |
Certificat Developer ID et notarisation requis, sinon Gatekeeper bloque l'app |
| Windows | NSIS .exe, MSI |
certificat de signature Code pour éviter les alertes SmartScreen |
| Linux | AppImage, .deb, .rpm |
Optionnel |
Les applications Electron sont plus volumineuses que les builds mobiles, car elles embarquent Chromium et Node.js. Comptez des installateurs de 80 à 150 Mo selon les cibles.
Étape 7 : Déployer des mises à jour web sans nouveaux installateurs
La plupart des modifications dans une app Capacitor sont des modifications web. Sur mobile, vous pouvez les déployer avec les live updates Capgo. Sur desktop, @capgo/electron-updater fait le même travail avec le même modèle : des canaux, un bundle intégré, des bundles téléchargés et un rollback automatique si le nouveau bundle n'appelle pas notifyAppReady().
bun add @capgo/electron-updater
Mettez à jour le processus principal pour laisser l'updater choisir le bundle à charger :
// 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());
Ajoutez le bridge de l'updater au même fichier preload :
// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';
exposeUpdaterAPI();
Et confirmez un démarrage réussi depuis l'app web, uniquement sur desktop :
import { isDesktop } from './desktop';
if (isDesktop()) {
const { requireUpdater } = await import('@capgo/electron-updater/renderer');
await requireUpdater().notifyAppReady();
}
Envoyez un nouveau bundle avec le même CLI que celui que vous utilisez sur mobile :
bun run build
bunx @capgo/cli@latest bundle upload --channel=production
Les mises à jour web ne peuvent pas modifier Electron lui-même, le processus principal ou le preload. Pour ces cas-là, déployez un nouvel installateur avec un auto-updater binaire. Notre guide sur Mises à jour automatiques Electron couvre ce cas, et la page du plugin electron-updater et docs couvre toutes les options.
Troubleshooting
Fenêtre vide dans l’application packagée. Les assets utilisent des chemins absolus. Définissez base: './' et recompilez. Ouvrez DevTools dans l’application packagée pour vérifier les 404 sur file:///assets/....
Les routes affichent une page vide après rechargement. Le router a tenté de charger /route depuis le disque. Passez au hash routing pour le desktop.
require is not defined ou des erreurs de preload. Le preload sandboxed ne peut pas charger de packages arbitraires à l’exécution. Bundlez-le dans un seul fichier, comme dans le script de compilation ci-dessus.
Un appel de plugin est rejeté avec « not implemented on web ». Ce plugin n’a pas d’implémentation web. Ajoutez un handler IPC dans le main process et appelez-le via window.desktop.
macOS indique que l’application est endommagée ou provient d’un développeur non identifié. L’application n’est ni signée ni notarisée. Configurez un certificat Developer ID et la notarisation dans electron-builder.
Updates roll back right after install. notifyAppReady() n’a pas été appelé dans le délai imparti (10 secondes par défaut). Appelez-le tôt au démarrage de votre application.
Récapitulatif
Vous n’avez pas besoin d’un package Capacitor dédié pour cibler le desktop. Un processus principal Electron minimal, un preload qui expose un API typé, des chemins d’assets relatifs et electron-builder vous donnent des installateurs pour les trois systèmes d’exploitation à partir du même build web. Ajoutez @capgo/electron-updater et l’application desktop bénéficie du même workflow live update que vos builds iOS et Android. Si vous hésitez encore sur le runtime desktop, lisez notre Comparaison Electron vs Tauri.