I created an application with electron and react. This application has an internal c++ server. In dev modality I don't have problems but after packaging the server not starts.
This is my folder tree:
├── dist-electron
│ ├── main
│ └── preload
├── electron
│ ├── main
│ └── preload
├── public
│ └── tmp
├── server
└── src
├── @types
├── api
├── assets
├── common
├── components
├── context
├── routes
└── theme
The server folder is included after react build into the dist folder.
In the electron main i wrote this function:
try {
const appPath = app.isPackaged
? join(process.env.PUBLIC, '../../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: process.env.PUBLIC });
app.quit();
}
where:
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
I always have spawn ENOTDIR as error.
I extract the app.asar file and the operations executable exists in the correct directory dist/server
This is the electron-builder.json5 configuration file:
/**
* @see https://www.electron.build/configuration/configuration
*/
{
"appId": "it.softwaves.dial-designer",
"productName": "Dial Designer",
"asar": true,
"directories": {
"output": "release/${version}"
},
"files": [
"dist-electron",
"dist",
"server"
],
"mac": {
"artifactName": "Dial_Designer_${version}.${ext}",
"target": [
"dmg",
"zip"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "Dial_Designer_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": {
"provider": "generic",
"channel": "latest",
"url": "https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/"
}
}
And this it the DialDesigner.ts class that instance and configure the electron application:
import { join, dirname } from 'node:path';
import { release } from 'node:os';
import { spawn } from 'node:child_process';
import { copySync } from 'fs-extra';
import {
app, BrowserWindow, screen,
shell, Menu, MenuItemConstructorOptions, dialog
} from 'electron';
import { update } from './update';
interface IWin {
title: string,
width: number,
height: number,
preload?: any,
resizable?: boolean,
}
export default class DialDesigner {
public static main: BrowserWindow | null;
public static settings: BrowserWindow | null;
public static url: string | undefined;
public static indexHtml: string;
public static IS_MAC: boolean = process.platform === 'darwin';
constructor() {
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
DialDesigner.url = process.env.VITE_DEV_SERVER_URL;
DialDesigner.indexHtml = join(process.env.DIST, 'index.html');
if (release().startsWith('6.1')) app.disableHardwareAcceleration();
if (process.platform === 'win32') app.setAppUserModelId(app.getName());
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
}
public static newWindow = (win: IWin): BrowserWindow => {
return new BrowserWindow({
title: win.title,
icon: join(process.env.PUBLIC, 'favicon.ico'),
width: win.width,
height: win.height,
center: true,
resizable: win.resizable ?? true,
webPreferences: {
preload: win?.preload,
nodeIntegration: true,
contextIsolation: false,
},
});
}
private runServer = () => {
let appPath = null;
try {
appPath = app.isPackaged
? join(process.env.DIST, '../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message:
`PUBLIC: ${process.env.PUBLIC}
DIST: ${process.env.DIST}
EXE: ${join(appPath!, 'operations')}
`
});
app.quit();
}
}
private createWindow = () => {
const { width, height } = screen.getPrimaryDisplay().size;
DialDesigner.main = DialDesigner.newWindow({
title: 'Dial Designer',
width, height,
preload: join(__dirname, '../preload/index.js'),
});
if (DialDesigner.url) {
DialDesigner.main.loadURL(DialDesigner.url);
DialDesigner.main.webContents.openDevTools();
}
else {
DialDesigner.main.loadFile(DialDesigner.indexHtml);
}
DialDesigner.main.webContents.on('did-finish-load', () => {
DialDesigner.main?.webContents.send('main-process-message', new Date().toLocaleString())
});
// Make all links open with the browser, not with the application
DialDesigner.main.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https:')) shell.openExternal(url)
return { action: 'deny' }
});
update(DialDesigner.main);
}
public initialize = () => {
try {
app.setPath('userData', `${app.getPath('appData')}/Dial Designer`);
app.whenReady()
.then(this.createWindow);
app.on('ready', this.runServer);
app.on('window-all-closed', () => {
DialDesigner.main = null
if (process.platform !== 'darwin') app.quit()
});
app.on('second-instance', () => {
if (DialDesigner.main) {
if (DialDesigner.main.isMinimized()) DialDesigner.main.restore();
DialDesigner.main.focus();
}
});
app.on('activate', () => {
const allWindows = BrowserWindow.getAllWindows()
if (allWindows.length) allWindows[0].focus();
else this.createWindow();
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: `${(err as Error).stack}` });
app.quit();
}
}
public applicationMenu = (template: MenuItemConstructorOptions[]) => {
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
}
I suspect this is caused by a bad path resulting from packaged vs unpackaged paths differing.
To trace the issue:
app.asarnpx @electron/asar extract app.asar appAsarFor example, in our code we use something like this:
export const preloadPath = path.join(
__dirname,
IS_DEV_ENV ? 'dist' : '',
'preload.build.js'
);
If that still doesn't work - add electron-unhandled to your application by adding these lines to your main and preload process scripts.
import unhandled from 'electron-unhandled';
unhandled();
Be sure to also run npm install --save electron-unhandled
This should show you a dialog box with the error that's occurring, if any.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With