This is an old revision of the document!
Base di partenza: configurazione package.json in stile LibrePM
Questa guida spiega come usare una configurazione npm + Electron + Vite + electron-builder simile a quella del progetto LibrePM per creare nuove applicazioni desktop che:
.deb installabili su Debian/Ubuntu e derivate in modo ordinato.jar, binari, template, file statici o altri asset di runtimeL’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.
Il package.json di LibrePM implementa una pipeline molto concreta:
AppImage, snap e deb (è possibile il flatpak ma non è molto stabile)desktop, utile per il file .desktop così da permettere l'inserimento di icone e altri oggetti utili alla buildgradlew bootJar, poi il frontend Vite, poi il packaging ElectronIn pratica è una soluzione ideale quando vuoi costruire una desktop app npm-based ma con capacità più enterprise rispetto a una semplice web app.
Di seguito i punti più importanti della configurazione di partenza e il loro significato operativo
{
"name": "nometuaapp-desktop",
"version": "0.1.0",
"description": "NomeTuaApp - Descrizione",
"type": "module",
"main": "electron/main.cjs"
}
name influenza il nome tecnico del pacchettoversion viene usata anche negli artefatti prodottidescription aiuta packaging e identificazionetype: “module” abilita l’uso di ESM nel progetto Node lato toolingmain indica l’entrypoint Electron principale
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:
"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"
}
dev avvia frontend Vite ed Electron insiemewait-on evita che Electron parta prima che il renderer sia disponibilebuild produce solo il bundle frontenddist esegue la pipeline completa:Per nuove app puoi mantenere lo stesso schema anche se:
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"
Usa questa struttura quando vuoi realizzare:
Non è invece la scelta più leggera se vuoi solo:
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
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.
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.
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.
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()
})
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('desktopAPI', {
ping: () => 'pong'
})
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
export default function App() {
return (
<main style={{ padding: 24, fontFamily: 'sans-serif' }}>
<h1>Nuova app desktop npm</h1>
<p>Base pronta per Electron + Vite + build .deb</p>
</main>
)
}
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.
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"
]
}
}
}
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"
}
}
Definisce dove verranno generati gli artefatti finali
Specifica cosa entra nel pacchetto applicativo
Se dimentichi dist//*, il processo principale può mancare
, la UI buildata non entra nel pacchetto
Se dimentichi electron//*
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
Serve per l’icona dell’app nei pacchetti Linux
Permette di personalizzare il file .desktop
Nel caso analizzato viene forzata una chiave Icon con un path assoluto nel filesystem Linux installato
La configurazione è valida perché segue quattro regole fondamentali
Il frontend viene compilato prima del packaging
Senza vite build, Electron non avrebbe i file statici finali da impacchettare.
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:
<code json>
“extraResources”: [
{
“from”: “../build/libs”,
“to”: “backend-libs”,
“filter”: [“*.jar”, “!*-plain.jar”]
}
]
</code>
==== 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 ====
<code json>
“extraResources”: [
{
“from”: “./runtime”,
“to”: “runtime”,
“filter”: [“/*”]
}
] </code>
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.
Nel package analizzato compare:
"desktop": {
"Icon": "/usr/share/icons/hicolor/1080x1080/apps/librepm-desktop.png"
}
Si sta dicendo al file .desktop quale icona usare dopo l’installazione del pacchetto.
Quando replichi questo pattern su una nuova app, devi mantenere coerenza tra:
IconNon 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.
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.
Nel caso analizzato la build è:
"dist": "cd .. && ./gradlew bootJar && cd desktop && vite build && electron-builder"
1. esce dalla cartella desktop 2. costruisce il backend Spring Boot 3. rientra in desktop 4. builda il frontend 5. genera gli installer
"dist": "vite build && electron-builder"
"dist": "npm run build:backend && vite build && electron-builder"
Nel flusso di sviluppo:
http://127.0.0.1:5173dist/index.htmlQuesto pattern è corretto perché separa bene:
if (isDev) {
win.loadURL('http://127.0.0.1:5173')
} else {
win.loadFile(path.join(__dirname, '../dist/index.html'))
}
Crea il progetto
mkdir my-desktop-app cd my-desktop-app npm init -y
Installa dipendenze
npm install react react-dom npm install -D electron electron-builder vite @vitejs/plugin-react concurrently wait-on
Crea cartelle
mkdir electron src mkdir -p src/assets
Aggiungi i file minimi
electron/main.cjselectron/preload.cjssrc/main.jsxsrc/App.jsxindex.htmlvite.config.js
Sostituisci package.json con il template operativo
Aggiungi icone reali
src/assets/icon.pngsrc/assets/icon.icosrc/assets/icon.icnsAvvia in sviluppo
npm run dev
Genera il pacchetto Linux
npm run dist
oppure solo target Linux Debian
npx electron-builder --linux deb
Dopo la generazione del pacchetto, non fermarti alla sola presenza del file. Controlla sempre:
dpkg -I dist-electron/*.deb
dpkg -c dist-electron/*.deb
sudo apt install ./dist-electron/*.deb
my-desktop-app
oppure il nome eseguibile effettivo installato dal pacchetto.
.desktop è correttoIl frontend non entra nel pacchetto
Causa comune:
vite build non eseguitofiles incompletoL’app si installa ma apre una finestra bianca
Cause comuni:
loadFile sbagliatodist non inclusoL’icona non compare
Cause comuni:
linux.icon non coerentedesktop.Icon con path sbagliatoLe risorse extra non si trovano dopo l’installazione
Causa comune:
process.resourcesPathIl backend viene compilato ma non incluso
Causa comune:
extraResources punta alla cartella sbagliataelectron-builder
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:
Schema di esempio:
"deb": {
"maintainer": "Nome Cognome <nome@example.com>",
"priority": "optional",
"packageCategory": "office"
}
Questa è una buona estensione quando il progetto smette di essere “solo una build locale” e diventa un prodotto distribuibile.
"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 <nome@example.com>",
"priority": "optional",
"packageCategory": "office"
}
}
Se vuoi usare questa configurazione come base per nuove app npm, ti consiglio di separare sempre il progetto in tre livelli
Renderer UI Tutto ciò che è React, Vite, componenti, pagine, store e logica di presentazione
Bridge
API minime esposte da preload.cjs al frontend
Main process Gestione finestre, file system, processi esterni, avvio backend, integrazioni desktop, notifiche, dialog, shell
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.
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:
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
}
Questo pattern va adattato con logging, healthcheck, shutdown controllato e gestione errori Però come base operativa è già utile e coerente con la configurazione studiata.
name correttoproductName correttoappId univocoversion aggiornataauthor compilatodist//* incluso
* electron//* inclusolinux.target contiene debdeb presentedesktop.Icon verificata davveroloadURL in sviluppoloadFile in produzioneprocess.resourcesPath usato per risorse extranpm run dev funzionanpm run dist funzionadpkg -I okdpkg -c okLa configurazione analizzata è una base molto valida per creare nuove applicazioni desktop npm-based perché unisce:
.debIl 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:
.deb{
"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 <you@example.com>",
"priority": "optional",
"packageCategory": "office"
}
}
}
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.
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