AnswerBun.com

Creating an installer for Electron React JS app - reactJS component doesn't load when run after install

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

Project Directory Structure

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.

Stack Overflow Asked by Boardy on January 28, 2021

2 Answers

2 Answers

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).

enter image description here

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

Add your own answers!

Related Questions

Appending a list in a list using a for loop

2  Asked on November 22, 2021 by syed-ahmed

 

Regular expression to find Specific character in a string

4  Asked on November 22, 2021 by user3061338

       

laravel count records based on each single date

1  Asked on November 22, 2021 by gulzar-ali

       

Change one property in CSS

2  Asked on November 22, 2021 by darek

 

Splitting values in a list and making variables of them

1  Asked on November 22, 2021 by premier12

     

JSX fragment has no corresponding closing tag

1  Asked on November 22, 2021 by chinwe-watkins

   

How to slow down window.location.href on AJAX request

3  Asked on November 22, 2021 by agiftel-longwave

     

Why list initialization disallow narrowing?

0  Asked on November 22, 2021 by alan-jian

   

How can i remove object from nested array?

2  Asked on November 22, 2021 by trajce12

 

How to save model architecture in PyTorch?

3  Asked on November 22, 2021

 

Ask a Question

Get help from others!

© 2022 AnswerBun.com. All rights reserved.