Aller au contenu principal

Créer une application desktop avec Capacitor et Electron (2026)

Transformez votre application Capacitor en application desktop avec Electron : configuration d'un shell sécurisé, stratégie de plugins, packaging pour macOS, Windows, Linux et live updates.

Bannière Capgo pour un guide sur la création d'une application desktop à partir d'un projet Capacitor avec Electron

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 build génère un dossier statique (le webDir dans capacitor.config.ts, souvent dist ou www).
  • 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 :

  1. Chemins d'assets relatifs. Avec Vite, configurez base: './' pour que le build index.html références ./assets/... au lieu de /assets/....
  2. Routage. Les deep links comme /settings n'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 chargement index.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.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.