====== Guida operativa per creare applicazioni npm desktop con Electron, Vite ed electron-builder ====== Base di partenza: configurazione ''package.json'' in stile LibrePM ===== 1. Scopo della guida ===== Questa guida spiega come usare una configurazione **npm + Electron + Vite + electron-builder** simile a quella del progetto LibrePM per creare nuove applicazioni desktop che: * si sviluppano con tooling npm moderno * usano una UI web locale renderizzata in Electron * generano build desktop multipiattaforma * producono anche pacchetti **''.deb''** installabili su Debian/Ubuntu e derivate in modo ordinato * possono includere anche risorse esterne, ad esempio un backend ''.jar'', binari, template, file statici o altri asset di runtime L’obiettivo non è solo “far partire Electron”, ma usare una struttura **replicabile per nuovi progetti** con una pipeline di build pulita, comprensibile e pronta per crescere. ===== 2. Cosa fa questa configurazione e perché è una buona base ===== Il ''package.json'' di LibrePM implementa una pipeline molto concreta: * **Vite** costruisce il frontend * **Electron** avvia l’app desktop * **electron-builder** impacchetta l’app per macOS, Windows e Linux * la build desktop include anche **risorse extra** esterne al bundle frontend * su Linux sono esplicitati i target **''AppImage''**, **''snap''** e **''deb''** (è possibile il **''flatpak''** ma non è molto stabile) * per Linux viene personalizzato anche il blocco **''desktop''**, utile per il file ''.desktop'' così da permettere l'inserimento di icone e altri oggetti utili alla build * in LibrePM la build completa lancia prima il backend Java con ''gradlew bootJar'', poi il frontend Vite, poi il packaging Electron * **NOTA BENE**: il backend Java può essere sostituito con uno Python/Go/altri linguaggi a scelta, purché si espliciti nel pacchetto e si includa poi tutto il necessario In pratica è una soluzione ideale quando vuoi costruire una **desktop app npm-based** ma con capacità più enterprise rispetto a una semplice web app. ===== 3. Anatomia della configurazione ===== Di seguito i punti più importanti della configurazione di partenza e il loro significato operativo ==== 3.1 Metadati di base ==== { "name": "nometuaapp-desktop", "version": "0.1.0", "description": "NomeTuaApp - Descrizione", "type": "module", "main": "electron/main.cjs" } ==== Perché conta ==== * ''name'' influenza il nome tecnico del pacchetto * ''version'' viene usata anche negli artefatti prodotti * ''description'' aiuta packaging e identificazione * ''type: "module"'' abilita l’uso di ESM nel progetto Node lato tooling * ''main'' indica l’entrypoint Electron principale ==== Nota importante ==== In questa configurazione il progetto usa **ESM a livello package** ma il processo principale Electron è in **CommonJS** con file ''.cjs'' È una scelta pratica e sensata quando vuoi: * tooling moderno lato frontend * meno attrito nel bootstrap Electron * compatibilità elevata con dipendenze e script di runtime ===== 4. Gli script che rendono la pipeline riusabile ===== ==== Script presenti ==== "scripts": { "dev": "concurrently -k \"vite --host 127.0.0.1\" \"wait-on http://127.0.0.1:5173 && electron .\"", "dev:renderer": "vite", "dev:electron": "wait-on http://localhost:5173 && electron .", "build": "vite build", "dist": "cd .. && ./gradlew bootJar && cd desktop && vite build && electron-builder", "preview": "vite preview" } ==== Logica operativa ==== * ''dev'' avvia frontend Vite ed Electron insieme * ''wait-on'' evita che Electron parta prima che il renderer sia disponibile * ''build'' produce solo il bundle frontend * ''dist'' esegue la pipeline completa: - build del backend - build del frontend - packaging desktop ==== Perché questa impostazione è utile anche per nuovi progetti ==== Per nuove app puoi mantenere lo stesso schema anche se: * non hai backend Java * hai un backend Node separato * vuoi solo una desktop app frontend-only Ti basta sostituire la parte centrale della pipeline Ad esempio: "dist": "vite build && electron-builder" oppure "dist": "npm run build:backend && vite build && electron-builder" ===== 5. Quando usare questa architettura ===== Usa questa struttura quando vuoi realizzare: * gestionali desktop * dashboard locali con database locale o backend embedded * software offline-first * tool professionali distribuiti come installer * applicazioni che devono installarsi come programma “vero” su Linux, Windows e macOS Non è invece la scelta più leggera se vuoi solo: * una SPA da deployare su hosting web * una utility CLI * una libreria npm ===== 6. Struttura progetto consigliata ===== Una struttura chiara per replicare questa configurazione è la seguente my-app/ ├─ desktop/ │ ├─ electron/ │ │ ├─ main.cjs │ │ └─ preload.cjs │ ├─ src/ │ │ ├─ assets/ │ │ │ ├─ icon.png │ │ │ ├─ icon.ico │ │ │ └─ icon.icns │ │ ├─ App.jsx │ │ └─ main.jsx │ ├─ dist/ │ ├─ dist-electron/ │ ├─ index.html │ ├─ package.json │ └─ vite.config.js ├─ backend/ │ └─ ... eventuale backend └─ build/ └─ libs/ └─ my-backend.jar ==== Osservazione importante ==== Nel caso di LibrePM, la cartella ''desktop/'' vive dentro un progetto più grande e la build desktop pesca il backend da: "from": "../build/libs" Questa è una soluzione ottima per applicazioni ibride in cui il pacchetto desktop deve incorporare un backend compilato altrove. ===== 7. Dipendenze principali e loro ruolo ===== ==== Dipendenze runtime ==== Nel progetto di partenza sono presenti React, i18n, Bootstrap, charting, drag and drop, datepicker, gantt e altri componenti di UI Per nuove app non devi copiarle tutte: devi selezionare solo ciò che serve. ==== Dev dependencies essenziali per replicare la pipeline ==== npm install -D electron electron-builder vite @vitejs/plugin-react concurrently wait-on npm install react react-dom Aggiungi poi solo i pacchetti necessari per la tua UI. ===== 8. File minimi per partire ===== ==== 8.1 ''electron/main.cjs'' ==== const { app, BrowserWindow } = require('electron') const path = require('path') const isDev = !app.isPackaged function createWindow() { const win = new BrowserWindow({ width: 1400, height: 900, webPreferences: { preload: path.join(__dirname, 'preload.cjs'), contextIsolation: true, nodeIntegration: false } }) if (isDev) { win.loadURL('http://127.0.0.1:5173') } else { win.loadFile(path.join(__dirname, '../dist/index.html')) } } app.whenReady().then(() => { createWindow() app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }) ==== 8.2 ''electron/preload.cjs'' ==== const { contextBridge } = require('electron') contextBridge.exposeInMainWorld('desktopAPI', { ping: () => 'pong' }) ==== 8.3 ''src/main.jsx'' ==== import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' ReactDOM.createRoot(document.getElementById('root')).render( ) ==== 8.4 ''src/App.jsx'' ==== export default function App() { return (

Nuova app desktop npm

Base pronta per Electron + Vite + build .deb

) }
se si è ferrati, è possibile usare TypeScript (che io personalmente conosco meno) è un modo più stabile di usare JavaScript (file .jsx) ma vale solo per progetti che diventano troppo grandi. In LibrePM l'uso del framework React, mi ha permesso di usare diversi workaround rispetto JavaScript e quindi sono riuscito comunque ad essere efficiente. ===== 9. ''package.json'' modello da cui partire ===== Questo esempio conserva la filosofia della configurazione analizzata, ma la rende più didattica e riutilizzabile { "name": "my-desktop-app", "version": "0.1.0", "description": "My Desktop App", "author": { "name": "Nome Cognome", "email": "nome@example.com" }, "type": "module", "main": "electron/main.cjs", "scripts": { "dev": "concurrently -k \"vite --host 127.0.0.1\" \"wait-on http://127.0.0.1:5173 && electron .\"", "build": "vite build", "dist": "vite build && electron-builder", "preview": "vite preview" }, "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", "concurrently": "^8.2.2", "electron": "^32.3.3", "electron-builder": "^25.0.5", "vite": "^5.4.2", "wait-on": "^7.2.0" }, "build": { "appId": "com.example.myapp", "productName": "MyApp", "directories": { "output": "dist-electron" }, "files": [ "dist/**/*", "electron/**/*" ], "extraResources": [], "mac": { "category": "public.app-category.productivity", "icon": "src/assets/icon.icns", "target": [ { "target": "dmg", "arch": ["x64", "arm64"] } ] }, "win": { "icon": "src/assets/icon.ico", "target": [ { "target": "nsis", "arch": ["x64"] } ] }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true, "shortcutName": "MyApp", "artifactName": "MyApp-Setup-${version}.${ext}" }, "linux": { "icon": "src/assets/icon.png", "category": "Office", "target": [ "AppImage", "snap", "deb" ] } } } ===== 10. Il cuore della build ''.deb'' ===== Per ottenere un ''.deb'' corretto, i blocchi più importanti sono questi "directories": { "output": "dist-electron" }, "files": [ "dist/**/*", "electron/**/*" ], "linux": { "icon": "src/assets/icon.png", "category": "Office", "target": [ "AppImage", "snap", "deb" ], "desktop": { "Icon": "/usr/share/icons/hicolor/1080x1080/apps/librepm-desktop.png" } } ==== Cosa fanno davvero ==== === ''directories.output'' === Definisce dove verranno generati gli artefatti finali === ''files'' === Specifica cosa entra nel pacchetto applicativo Se dimentichi ''dist/**/*'', la UI buildata non entra nel pacchetto Se dimentichi ''electron/**/*'', il processo principale può mancare === ''linux.target'' === Se inserisci ''deb'', electron-builder produrrà anche il pacchetto Debian Se non lo specifichi, i target di default su Linux non coincidono necessariamente con ciò che vuoi distribuire === ''linux.icon'' === Serve per l’icona dell’app nei pacchetti Linux === ''linux.desktop'' === Permette di personalizzare il file ''.desktop'' Nel caso analizzato viene forzata una chiave ''Icon'' con un path assoluto nel filesystem Linux installato ===== 11. Perché questa configurazione funziona bene anche per i ''.deb'' ===== La configurazione è valida perché segue quattro regole fondamentali ==== Regola 1 ==== Il frontend viene compilato prima del packaging Senza ''vite build'', Electron non avrebbe i file statici finali da impacchettare. ==== Regola 2 ==== Il main process Electron è incluso tra i file del pacchetto "electron/../." garantisce che il bootstrap desktop venga distribuito. ==== Regola 3 ==== I target Linux sono dichiarati esplicitamente Avere ''deb'' nel blocco ''linux.target'' evita ambiguità e rende la pipeline coerente con l’obiettivo di distribuzione Debian. ==== Regola 4 ==== Le risorse aggiuntive possono essere incluse ''extraResources'' consente di trasportare nel pacchetto file che non fanno parte del bundle frontend, ad esempio un ''.jar''. ===== 12. Come includere backend, eseguibili o asset con ''extraResources'' ===== Il progetto di partenza usa: "extraResources": [ { "from": "../build/libs", "to": "backend-libs", "filter": ["*.jar", "!*-plain.jar"] } ] ==== Quando usare questo approccio ==== Usa ''extraResources'' quando devi includere nel pacchetto finale: * backend Java (o diverso da Java) compilato * template documentali * binari nativi * modelli ML locali * database seed * configurazioni di runtime * licenze o file di supporto ==== Vantaggio pratico ==== In questo modo non stai “simulando” il backend lato frontend, ma stai distribuendo una vera applicazione desktop composta da più parti. ==== Esempio riutilizzabile per una nuova app ==== "extraResources": [ { "from": "./runtime", "to": "runtime", "filter": ["**/*"] } ] ===== 13. Come leggere le risorse extra a runtime ===== Quando l’app è impacchettata, i file extra non vanno cercati come in sviluppo. Conviene usare ''process.resourcesPath''. Esempio lato main process const path = require('path') const { app } = require('electron') function getBackendJarPath() { if (app.isPackaged) { return path.join(process.resourcesPath, 'backend-libs', 'my-backend.jar') } return path.join(__dirname, '../../build/libs/my-backend.jar') } Questa distinzione è essenziale per non rompere il ''.deb'' installato. ===== 14. ''linux.desktop.Icon'' e perché va trattato con attenzione ===== Nel package analizzato compare: "desktop": { "Icon": "/usr/share/icons/hicolor/1080x1080/apps/librepm-desktop.png" } ==== Cosa significa ==== Si sta dicendo al file ''.desktop'' quale icona usare dopo l’installazione del pacchetto. ==== Buona pratica operativa ==== Quando replichi questo pattern su una nuova app, devi mantenere coerenza tra: * nome del pacchetto * nome dell’icona installata * path indicato nella chiave ''Icon'' * asset realmente presenti nel pacchetto finale ==== Regola fondamentale ==== Non copiare il path dell’icona “così com’è” da un progetto all’altro Devi sempre verificare che il pacchetto generato installi davvero quel file in quel percorso. ==== Come verificarlo ==== Dopo la build: dpkg -c dist-electron/*.deb | grep -i icon Se il path reale non coincide con quello del file ''.desktop'', il pacchetto può installarsi ma mostrare icona mancante nel menu di sistema. ===== 15. Comando completo di distribuzione ===== Nel caso analizzato la build è: "dist": "cd .. && ./gradlew bootJar && cd desktop && vite build && electron-builder" ==== Interpretazione ==== 1. esce dalla cartella desktop 2. costruisce il backend Spring Boot 3. rientra in desktop 4. builda il frontend 5. genera gli installer ==== Variante consigliata per nuovi progetti solo npm ==== "dist": "vite build && electron-builder" ==== Variante con backend Node locale ==== "dist": "npm run build:backend && vite build && electron-builder" ===== 16. Vite ed Electron insieme: regola architetturale corretta ===== Nel flusso di sviluppo: * Vite serve la UI da ''http://127.0.0.1:5173'' * Electron apre quell’URL in dev * in produzione Electron carica invece ''dist/index.html'' Questo pattern è corretto perché separa bene: * esperienza di sviluppo * bundle di produzione ==== Pattern da replicare sempre ==== if (isDev) { win.loadURL('http://127.0.0.1:5173') } else { win.loadFile(path.join(__dirname, '../dist/index.html')) } ===== 17. Creare nuove app partendo da questa struttura ===== ==== Procedura rapida ==== ==== Step 1 ==== Crea il progetto mkdir my-desktop-app cd my-desktop-app npm init -y ==== Step 2 ==== Installa dipendenze npm install react react-dom npm install -D electron electron-builder vite @vitejs/plugin-react concurrently wait-on ==== Step 3 ==== Crea cartelle mkdir electron src mkdir -p src/assets ==== Step 4 ==== Aggiungi i file minimi * ''electron/main.cjs'' * ''electron/preload.cjs'' * ''src/main.jsx'' * ''src/App.jsx'' * ''index.html'' * ''vite.config.js'' ==== Step 5 ==== Sostituisci ''package.json'' con il template operativo ==== Step 6 ==== Aggiungi icone reali * ''src/assets/icon.png'' * ''src/assets/icon.ico'' * ''src/assets/icon.icns'' ==== Step 7 ==== Avvia in sviluppo npm run dev ==== Step 8 ==== Genera il pacchetto Linux npm run dist oppure solo target Linux Debian npx electron-builder --linux deb ===== 18. Verifica seria del ''.deb'' ===== Dopo la generazione del pacchetto, non fermarti alla sola presenza del file. Controlla sempre: ==== Metadati ==== dpkg -I dist-electron/*.deb ==== Contenuto del pacchetto ==== dpkg -c dist-electron/*.deb ==== Installazione locale ==== sudo apt install ./dist-electron/*.deb ==== Avvio da terminale ==== my-desktop-app oppure il nome eseguibile effettivo installato dal pacchetto. ==== Controlli manuali fondamentali ==== * l’app si apre * l’icona compare nel menu * il file ''.desktop'' è corretto * il frontend è caricato * le risorse extra sono leggibili * eventuali processi backend partono davvero * la disinstallazione non lascia stato inconsistente ===== 19. Errori tipici che rompono i ''.deb'' ===== ==== Errore 1 ==== Il frontend non entra nel pacchetto Causa comune: * ''vite build'' non eseguito * ''files'' incompleto ==== Errore 2 ==== L’app si installa ma apre una finestra bianca Cause comuni: * path di ''loadFile'' sbagliato * ''dist'' non incluso * asset frontend referenziati con path incompatibili ==== Errore 3 ==== L’icona non compare Cause comuni: * ''linux.icon'' non coerente * chiave ''desktop.Icon'' con path sbagliato * nome file icona diverso dal nome installato nel pacchetto ==== Errore 4 ==== Le risorse extra non si trovano dopo l’installazione Causa comune: * codice che usa path da sviluppo anche in produzione * mancato uso di ''process.resourcesPath'' ==== Errore 5 ==== Il backend viene compilato ma non incluso Causa comune: * ''extraResources'' punta alla cartella sbagliata * filtro troppo restrittivo * il backend non è stato buildato prima di ''electron-builder'' ===== 20. Quando aggiungere un blocco ''deb'' dedicato ===== Per una prima build funzionante può bastare il blocco ''linux'' con target ''deb'' Per una distribuzione più professionale conviene aggiungere anche un blocco ''deb'' separato, ad esempio per gestire meglio: * maintainer * priority * package category * dipendenze consigliate * script post install o post remove Schema di esempio: "deb": { "maintainer": "Nome Cognome ", "priority": "optional", "packageCategory": "office" } Questa è una buona estensione quando il progetto smette di essere “solo una build locale” e diventa un prodotto distribuibile. ===== 21. Esempio completo più robusto per Linux Debian ===== "build": { "appId": "com.example.myapp", "productName": "MyApp", "directories": { "output": "dist-electron" }, "files": [ "dist/**/*", "electron/**/*" ], "extraResources": [ { "from": "./runtime", "to": "runtime", "filter": ["**/*"] } ], "linux": { "icon": "src/assets/icon.png", "category": "Office", "target": [ "AppImage", "snap", "deb" ] }, "deb": { "maintainer": "Nome Cognome ", "priority": "optional", "packageCategory": "office" } } ===== 22. Consiglio architetturale per scrivere nuovo codice ===== Se vuoi usare questa configurazione come base per **nuove app npm**, ti consiglio di separare sempre il progetto in tre livelli ==== Livello 1 ==== Renderer UI Tutto ciò che è React, Vite, componenti, pagine, store e logica di presentazione ==== Livello 2 ==== Bridge API minime esposte da ''preload.cjs'' al frontend ==== Livello 3 ==== Main process Gestione finestre, file system, processi esterni, avvio backend, integrazioni desktop, notifiche, dialog, shell ==== Regola d’oro ==== Non mettere logica desktop o privilegiata direttamente nel renderer Tieni il renderer il più possibile come una web app locale e delega al bridge solo ciò che serve davvero. ===== 23. Pattern consigliato per nuove app con backend embedded ===== Se la tua nuova applicazione deve includere un backend locale, la pipeline consigliata è questa 1. build backend 2. copia backend tra le ''extraResources'' 3. avvio backend dal main process Electron 4. il renderer comunica con il backend tramite HTTP locale o IPC strutturato Questo pattern è particolarmente forte per: * gestionali locali * software offline-first * prodotti enterprise installabili * app con database locale * sistemi documentali o PM tool ===== 24. Esempio di avvio backend dal main process ===== const { spawn } = require('child_process') const path = require('path') const { app } = require('electron') function getBackendPath() { if (app.isPackaged) { return path.join(process.resourcesPath, 'backend-libs', 'my-backend.jar') } return path.join(__dirname, '../../build/libs/my-backend.jar') } function startBackend() { const backendPath = getBackendPath() const child = spawn('java', ['-jar', backendPath], { detached: false, stdio: 'ignore' }) return child } ==== Nota ==== Questo pattern va adattato con logging, healthcheck, shutdown controllato e gestione errori Però come base operativa è già utile e coerente con la configurazione studiata. ===== 25. Checklist finale prima di considerare “buona” una base template ===== ==== Metadati ==== * ''name'' corretto * ''productName'' corretto * ''appId'' univoco * ''version'' aggiornata * ''author'' compilato ==== File ==== * ''dist/**/*'' incluso * ''electron/**/*'' incluso * icone presenti * entrypoint Electron corretto ==== Linux Debian ==== * ''linux.target'' contiene ''deb'' * categoria Linux coerente * icona Linux coerente * eventuale blocco ''deb'' presente * eventuale chiave ''desktop.Icon'' verificata davvero ==== Runtime ==== * ''loadURL'' in sviluppo * ''loadFile'' in produzione * ''process.resourcesPath'' usato per risorse extra * eventuale backend incluso davvero ==== Validazione ==== * ''npm run dev'' funziona * ''npm run dist'' funziona * ''dpkg -I'' ok * ''dpkg -c'' ok * installazione locale ok * avvio post install ok ===== 26. Conclusione operativa ===== La configurazione analizzata è una **base molto valida** per creare nuove applicazioni desktop npm-based perché unisce: * semplicità di sviluppo con Vite * packaging serio con Electron * distribuzione multipiattaforma con electron-builder * supporto concreto ai pacchetti ''.deb'' * possibilità di includere backend o asset extra nel pacchetto finale Il vero valore di questa impostazione non è solo generare un installer, ma costruire una **struttura standardizzabile** per una famiglia di applicazioni desktop professionali. Se vuoi riutilizzarla bene, non devi limitarti a copiare il ''package.json'' Devi invece replicare consapevolmente questi principi: * build separata tra renderer e runtime desktop * packaging esplicito * risorse extra gestite correttamente * path di produzione verificati * controllo reale del contenuto del ''.deb'' ===== 27. Mini template finale “production-ready starter” ===== { "name": "starter-desktop-app", "version": "0.1.0", "description": "Starter desktop app", "author": { "name": "Your Name", "email": "you@example.com" }, "type": "module", "main": "electron/main.cjs", "scripts": { "dev": "concurrently -k \"vite --host 127.0.0.1\" \"wait-on http://127.0.0.1:5173 && electron .\"", "build": "vite build", "dist": "vite build && electron-builder", "dist:deb": "vite build && electron-builder --linux deb" }, "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.1", "concurrently": "^8.2.2", "electron": "^32.3.3", "electron-builder": "^25.0.5", "vite": "^5.4.2", "wait-on": "^7.2.0" }, "build": { "appId": "com.example.starterdesktopapp", "productName": "StarterDesktopApp", "directories": { "output": "dist-electron" }, "files": [ "dist/**/*", "electron/**/*" ], "linux": { "icon": "src/assets/icon.png", "category": "Office", "target": [ "deb" ] }, "deb": { "maintainer": "Your Name ", "priority": "optional", "packageCategory": "office" } } } ===== 28. Riepilogo in una frase ===== Usa questa configurazione come **template per applicazioni desktop npm con Electron**, non come semplice file da copiare: è una pipeline di prodotto, non solo una dipendenza di build. ===== 29. Copyright & Release ===== Questo documento viene rilasciato con copyright © 2026 di BoostMedia APS ETS. Guida rilasciata con licenza GPL3 Per eventuali modifiche aggiungersi qui sotto con data: * Autore originale: Lorenzo DM * Release: 1.0.0 * Rilasciata il: 31/03/2026