My solution to this was to wrap the node express app (which should be started on electron app launch) as an npm package say offline-file-server . I then installed this package in my electron app as shown below:
package.json - for electron app
{
"name": "my-react-app-desktop",
"version": "1.0.0",
"private": true,
"main": "./main.js",
"homepage": "./",
"scripts": {
"start:dev": "electron .",
"clean": "rm -rf ./dist",
"build": "rollup -c",
"pack": "electron-builder"
},
"devDependencies": {
"electron": "^11.5.0",
"electron-builder": "^23.6.0",
"rollup": "^2.60.0",
"rollup-plugin-peer-deps-external": "^2.2.4"
},
"dependencies": {
"electron-log": "^4.4.1",
"fix-path": "^4.0.0",
"offline-file-server": "^1.0.0"
}
}
I then imported the package into electron app main.js file as follows:
const serverPath = path.join(__dirname,'node_modules/offline-file-server/dist/server.js');
const { fork } = require("child_process");
let startOfflineServer = () => {
const child = fork(serverPath, [], {
silent: true,
detached: true,
stdio: 'ignore',
env:{
...process.env,
PORT:3001
}
});
}
My actual issue was not including this section in the code above:
env:{
...process.env,
PORT:3001
}
this is because for some weird reason, while nodejs main process have access to process.env variables, the child process created using the fork method does not, even when the doc says that the default behavior is that child process inherits the parent process.env. At least it did not work in my case, hence, the need to explicitly provide it using the env option in the fork method.
My full electron main.js code now look like the following:
main.js
const { app, BrowserWindow } = require('electron');
const log = require('electron-log');
const path = require('path');
const serverPath = path.join(__dirname,'node_modules/offline-file-server/dist/server.js');
const { fork } = require("child_process");
let startOfflineServer = () => {
const child = fork(serverPath, [], {
silent: true,
detached: true,
stdio: 'ignore',
env:{
...process.env,
PORT:3001
}
});
child.on('error', (err) => {
log.info("\n\t\tERROR: spawn failed! (" + err + ")");
});
child.on('data', function(data) {
log.info('stdout: ' +data);
});
child.on('exit', (code, signal) => {
log.info('exit code : ',code);
log.info('exit signal : ',signal);
});
child.unref();
//on parent process exit, terminate child process too.
process.on('exit',function(){
child.kill()
})
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow = null;
function createWindow() {
//start offline server on a separate process(child process).
startOfflineServer();
mainWindow = new BrowserWindow({
alwaysOnTop: true,
webPreferences: {
allowRunningInsecureContent: true,
backgroundThrottling: false,
nodeIntegration: true
},
kiosk: false,
frame: false
});
mainWindow.loadURL('your_web_app_url');
mainWindow.on('closed', function () {
mainWindow = null;
});
mainWindow.on('page-title-updated', function (e) {
e.preventDefault();
});
mainWindow.once('ready-to-show', () => {
mainWindow.show()
});
}
// Start the app automatically when the system started.
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false });
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
I hope this helps.