Friday, 16 October 2020

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.



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

No comments:

Post a Comment