Stack Overflow Asked by Boardy on January 28, 2021
I am working on a new project using Electron and ReactJS. The project works fine in development mode, but I am trying to create an installer for Windows but no matter what I try and what I find on Google nothing works. I just get a blank white screen.
Below is my pacakge.json
{
"name": "MyApp",
"description": "My App Description",
"version": "0.1.2",
"private": true,
"homepage": "./",
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^26.0.14",
"@types/node": "^14.11.2",
"@types/react": "^16.9.50",
"@types/react-dom": "^16.9.8",
"bootstrap": "^4.5.2",
"electron-is-dev": "^1.2.0",
"electron-settings": "^4.0.2",
"electron-squirrel-startup": "^1.0.0",
"react": "^16.13.1",
"react-bootstrap": "^1.3.0",
"react-dom": "^16.13.1",
"react-icons": "^3.11.0",
"react-json-pretty": "^2.2.0",
"react-scripts": "3.4.3",
"react-tooltip": "^4.2.10",
"typescript": "^4.0.3"
},
"main": "src/electron-starter.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron-start": "set ELECTRON_START_URL=http://localhost:3000 && electron .",
"package-win": "electron-packager . --asar --out=release-builds --platform=win32 --arch=x64 --no-prune --ignore=/e2e --overwrite",
"create-installer-win": "node installers/windows/createInstaller.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"electron": "^10.1.3",
"electron-packager": "^12.0.1",
"electron-winstaller": "^4.0.1",
"react-router-dom": "^5.2.0",
"react-toastify": "^6.0.8"
}
}
My electron-start.js script is below
const {electron, Menu, app, BrowserWindow} = require('electron');
// Module to control application life.
//const app = electron.app;
// Module to create native browser window.
//const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
if (require('electron-squirrel-startup')) app.quit()
// if first time install on windows, do not run application, rather
// let squirrel installer do its work
const setupEvents = require('../installers/setupEvents')
if (setupEvents.handleSquirrelEvent()) {
console.log("Squirrel event returned true");
process.exit()
//return;
}
console.log("Starting main program");
// 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;
/*const env = process.env.NODE_ENV;
let windowUrlBase = "";
if (env === "production")
{
windowUrlBase = "/";
}
else
{
windowUrlBase = 'http://localhost:3000';
}*/
let windowUrlBase = 'http://localhost:3000';
function returnMainWindow()
{
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
//preload: __dirname + '/preload.tsx'
}
});
//const env = process.env.NODE_ENV;
//console.log("Environment: " + env);
const isDev = require('electron-is-dev');
windowUrlBase = "";
console.log("Not electron dev");
console.log("dir name: " + __dirname);
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
});
mainWindow.loadURL(startUrl);
return mainWindow;
}
function createWindow() {
// Create the browser window.
mainWindow = returnMainWindow();
mainWindow.maximize();
// and load the index.html of the app.
// Open the DevTools.
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
setMainMenu();
}
function setMainMenu()
{
const template = [
{
label: 'File',
submenu: [
{
label: 'Exit',
accelerator: "ctrl+f4",
click() {
app.quit();
}
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Settings',
click() {
mainWindow.loadURL(windowUrlBase + "/settings");
}
}
]
},
{
label: 'Help',
submenu: [
{
label: 'Show Dev Console',
accelerator: "f11",
click() {
mainWindow.webContents.openDevTools();
}
}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
// 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 OS X 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 OS X 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()
}
});
My create installer script is below
const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller
const path = require('path')
getInstallerConfig()
.then(createWindowsInstaller)
.catch((error) => {
console.error(error.message || error)
process.exit(1)
})
function getInstallerConfig () {
console.log('creating windows installer')
const rootPath = path.join('./')
const outPath = path.join(rootPath, 'release-builds')
return Promise.resolve({
appDirectory: path.join(outPath, 'crash-catch-control-panel-win32-x64'),
authors: 'Boardies IT Solutions',
noMsi: true,
outputDirectory: path.join(outPath, 'windows-installer'),
exe: 'crash-catch-control-panel.exe',
setupExe: 'crash-catch-control-panel-installer.exe'
//setupIcon: path.join(rootPath, 'assets', 'images', 'logo.ico')
})
}
My setupEvents.js is below
const electron = require('electron')
const app = electron.app
module.exports = {
handleSquirrelEvent: function() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Remove desktop and start menu shortcuts
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
}
}
My App.js is below
import * as React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom'
import './Stylesheet.css'
import Dashboard from "./Components/Dashboard";
import Settings from "./Components/Settings";
import './ComponentStyles/BreadcrumbNav.css'
import 'react-toastify/dist/ReactToastify.min.css';
import { ToastContainer, toast } from 'react-toastify';
import CustomerDetails from "./Components/CustomerDetails";
toast.configure({
position: 'top-center',
hideProgressBar: true
});
function App() {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" render={() => <Dashboard /> } exact />
<Route path="/customer-information/:customer_id" render={(props) => <CustomerDetails {...props} /> } exact />
<Route path="/settings" render={() => <Settings /> } exact />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
When I look at the chrome console when the app loads I see the following error:
Not allowed to load local resource:
file:///C:/Users/Chris/AppData/Local/MyApp/app-0.1.2/resources/app.asar/index.html
As mentioned above the problem only happens when I launch the electron app when its installed. If I launch it as part of the Node dev server then it works perfectly fine.
UPDATE
Directory structure below as requested
The components directory contains the actual ReactJS components and the directory ComponentStyles are all the individual component stylesheets. The components are typescript so are in tsx format.
After a hell of a lot of trial and error I've managed to get it working finally.
The first thing was to change my App.js so that instead of using BrowserRouter I now use HashRouter as follows and use the history from react-router-dom
import {HashRouter, useHistory} from "react-router-dom";
const history = createBrowserHistory();
<HashRouter basename="/" history={history} >
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/customer-information/:customer_id" component={CustomerDetails} />
<Route path="/settings" component={Settings} />
<Route path="*" component={NotFound} />
</Switch>
</HashRouter>
Then in each component when I want to change location I do the following:
const history = useHistory();
history.push('/newlocation');
When I load the browser window I now have the following:
mainWindow.loadURL(isDev ? windowUrlBase : `file://${__dirname}/../build/index.html`);
And removed the following block
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
});
and isDev is set using the following:
const isDev = require('electron-is-dev');
For the settings menu changing the location this was using IPC using the following:
in electron-start.js
const { ipcMain } = require("electron");
submenu: [
{
label: 'Settings',
click() {
//mainWindow.loadURL(windowUrlBase + "/settings");
//history.push('/settings')
//location.pathname = "/settings";
//ipcMain.send("change-location", "/settings");
mainWindow.webContents.send('change-location', '/settings')
//mainWindow.location.pathname = "/settings";
}
}
]
Then in a component that is shared across the project I have the following to receive the IPC event
const {ipcRenderer} = window.require('electron')
const history = useHistory();
useEffect(() => {
ipcRenderer.on('change-location', (event, arg) => {
history.push(arg);
});
})
Correct answer by Boardy on January 28, 2021
Are you sure this line is correct?
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
})
I think the pathname should be path.join(__dirname, './index.html')
The double ..
gets you two levels above (red), not to the parent folder (orange).
You can test this with a simple file in folders /main/sub/test.js:
const path = require('path')
console.log(__dirname)
console.log(path.join(__dirname, '../'))
console.log(path.join(__dirname, './'))
the output would be:
~/main/sub
~/main/
~/main/sub/
So, in your case, the ../index.html
gets your to /crash-catch-control-panel/
but index.html
isn't located there.
I am not familiar with using asar, but I think your error message: file:///C:/Users/Chris/AppData/Local/MyApp/app-0.1.2/resources/app.asar/index.html
indicates that you are not in the src
folder where your index.html
file is, but you are looking for index.html
one level above it.
Especially because what you have in your response: mainWindow.loadURL(isDev ? windowUrlBase : `file://${__dirname}/../build/index.html`)
goes up one level then drops into build
. So my concern is that here, you are jumping into your build
folder to get the index.html from the build
folder. But the build
folder is at the same hierarchy as your src
folder and you weren't pointed to that.
Answered by Andrew on January 28, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP