Sur macOS, l'icône d'une app Electron ne devrait pas être traitée comme une image qu'on force au runtime. Pour une app packagée, le résultat le plus propre vient d'un vrai fichier .icns déclaré dans le bundle .app.
Le réflexe classique consiste à ajouter un truc du genre dans electron/main.cts :
app.dock.setIcon(...)
Ça peut dépanner en dev. Mais pour une app distribuée, ce n'est pas la bonne source de vérité. macOS ne présente pas une app packagée comme une fenêtre Electron lancée à la main : il lit les métadonnées du bundle, son Info.plist, ses ressources, ses caches d'icônes, puis décide comment afficher l'app dans le Dock, Finder, Spotlight, Launchpad et les dialogues système.
Si tu veux un rendu natif, il faut donc jouer le jeu natif.
La règle
Pour HQ, la source de vérité est :
electron/assets/icon.icns
Et autour :
electron/assets/icon.png
electron/assets/icon-source/*
Le rôle de chaque fichier :
| Fichier | Rôle |
|---|---|
electron/assets/icon.icns | Icône macOS finale pour l'app packagée |
electron/assets/icon.png | Icône générique pour Electron, navigateur, previews |
electron/assets/icon-source/* | Sources éditables : fond, foreground, exports intermédiaires |
Le .icns est le fichier que macOS attend pour une vraie app. Le .png est utile, mais il ne remplace pas le bundle icon.
Pourquoi pas app.dock.setIcon
app.dock.setIcon(...) agit au runtime. C'est donc une instruction donnée à l'app quand elle tourne déjà.
Problème : l'icône d'une app macOS packagée n'existe pas seulement "quand l'app tourne". Elle existe aussi dans le Finder, Launchpad, Spotlight, les permissions système, les caches d'icônes et les previews du bundle.
Avec un override runtime, tu peux obtenir un Dock correct en dev et un comportement différent une fois l'app packagée. Ou l'inverse. C'est précisément le genre de bug visuel qui fait perdre du temps parce qu'on teste le mauvais état.
La règle pratique :
- Dev rapide :
app.dock.setIcon(...)peut servir de preview temporaire. - App normale : ne pas le mettre dans le chemin principal.
- Test final : toujours juger une
.apppackagée avec son.icns.
Pipeline actuel
Le pipeline se lance avec :
bun run icon:generate
Le générateur est dans :
scripts/generate-icon.ts
Il produit :
electron/assets/icon.svg
electron/assets/icon.png
electron/assets/icon.icns
electron/assets/icon-source/background.svg
electron/assets/icon-source/background.png
electron/assets/icon-source/foreground.svg
electron/assets/icon-source/foreground.png
electron/assets/icon-source/README.md
L'idée est simple : on garde des sources éditables, on exporte les formats intermédiaires, puis on fabrique un .icns moderne pour macOS.
Quand app-builder-bin est disponible, le script l'utilise pour générer l'icône finale. Ça permet d'obtenir un .icns récent, avec un type ic11, au lieu d'un fichier bricolé ou incomplet.
Vérifier l'icône
Après génération :
file electron/assets/icon.icns
Le résultat attendu ressemble à :
electron/assets/icon.icns: Mac OS X icon, ... "ic11" type
Ce check est volontairement bas niveau. Il évite de se contenter d'un "ça s'ouvre chez moi" alors que le fichier produit n'est pas dans la forme attendue par macOS moderne.
Dev Electron vs app packagée
bun run electron:dev lance Electron directement. Dans ce mode, macOS peut afficher une icône Electron, une icône cachée, ou un état qui dépend de ses caches locaux.
Ce n'est pas le test final.
Le vrai test :
- Générer l'icône.
- Packager l'app.
- Ouvrir la
.app. - Vérifier le Dock, Finder, Spotlight et Launchpad.
Si tu forces une icône en dev pour ton confort, documente-le comme override de développement. Ne le laisse pas devenir la logique normale de l'app.
Ce qu'il ne faut pas remettre
Dans le chemin principal de electron/main.cts, ne remets pas :
app.dock.setIcon(...)
La configuration de packaging doit pointer vers :
electron/assets/icon.icns
Et macOS doit lire l'icône depuis le bundle.
Mettre à jour l'icône
Le workflow propre :
- Modifier
scripts/generate-icon.ts. - Lancer
bun run icon:generate. - Vérifier que
electron/assets/icon.icnsest bien un fichieric11. - Packager l'app.
- Juger le résultat sur la
.app, pas surelectron:dev.
Ça paraît plus strict qu'un simple setIcon, mais c'est justement ce qui évite les écarts entre dev, packaging et comportement natif macOS.