'Electron: Why does showing localhost only work if never loaded another URL before?

In my Electron app I want to load an URL after the app has started:

mainWindow.loadURL(`file:///${process.resourcesPath}/Content/index.html`);

And later I want to show the file which is served on localhost:

mainWindow.loadURL(`http://127.0.0.1:2015/`);

I found out that showing localhost only works if I never load another URL before. And it does not matter if I load a local index.html or an online URL before showing localhost.

Does someone know how I can resolve this?

Here is my code. The problem is in the StartCaddy function. This function gets called when pressing on a menu item in the file menu.

// This only works if never used mainWindow.loadURL before???
mainWindow.loadURL("http://127.0.0.1:2015/");

If I comment out following line than it works without problem:

mainWindow.loadURL(windowURL);
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')

let mainWindow;
var windowURL = "https://www.google.com/";

function createWindow() {
  // Create the browser window.
  //const mainWindow = new BrowserWindow({
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    title: "", // Remove title in title bar
    //frame: false,
    //titleBarStyle: 'hidden',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // MENU BEGIN /////////////////////////////////////////////
  const { app, Menu } = require('electron')

  const isMac = process.platform === 'darwin'

  const template = [
    // { role: 'appMenu' }
    ...(isMac ? [{
      label: app.name,
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideOthers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' }
      ]
    }] : []),
    // { role: 'fileMenu' }
    {
      label: 'File',
      submenu: [
        {
          label: 'Start Caddy Server', click() {
            StartCaddy();
          }
        },
        {
          label: 'Git Action', click() {
            GitAction();
          }
        },
        {
          label: 'ZIP Action', click() {
            ZipAction();
          }
        },
        isMac ? { role: 'close' } : { role: 'quit' }
      ]
    },
    // { role: 'editMenu' }
    {
      label: 'Edit',
      submenu: [
        { role: 'undo' },
        { role: 'redo' },
        { type: 'separator' },
        { role: 'cut' },
        { role: 'copy' },
        { role: 'paste' },
        ...(isMac ? [
          { role: 'pasteAndMatchStyle' },
          { role: 'delete' },
          { role: 'selectAll' },
          { type: 'separator' },
          {
            label: 'Speech',
            submenu: [
              { role: 'startSpeaking' },
              { role: 'stopSpeaking' }
            ]
          }
        ] : [
          { role: 'delete' },
          { type: 'separator' },
          { role: 'selectAll' }
        ])
      ]
    },
    // { role: 'viewMenu' }
    {
      label: 'View',
      submenu: [
        { role: 'reload' },
        { role: 'forceReload' },
        { role: 'toggleDevTools' },
        { type: 'separator' },
        { role: 'resetZoom' },
        { role: 'zoomIn' },
        { role: 'zoomOut' },
        { type: 'separator' },
        { role: 'togglefullscreen' }
      ]
    },
    // { role: 'windowMenu' }
    {
      label: 'Window',
      submenu: [
        { role: 'minimize' },
        { role: 'zoom' },
        ...(isMac ? [
          { type: 'separator' },
          { role: 'front' },
          { type: 'separator' },
          { role: 'window' }
        ] : [
          { role: 'close' }
        ])
      ]
    },
    {
      role: 'help',
      submenu: [
        {
          label: 'Learn More',
          click: async () => {
            const { shell } = require('electron')
            await shell.openExternal('https://electronjs.org')
          }
        }
      ]
    }
  ]

  const menu = Menu.buildFromTemplate(template)
  Menu.setApplicationMenu(menu)
  // MENU END ////////////////////////////////////////////////

  // and load the index.html of the app.
  mainWindow.loadURL(windowURL);

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// 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.whenReady().then(() => {
  //windowURL = `file:///${process.resourcesPath}/Content/index.html`;
  createWindow()

  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 (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

// FUNCTIONS ///////////////////////////////////////////////

// Path to the resources directory (works only in builded app)
// ${process.resourcesPath}

// Path to the project's root folder
// ${__dirname}

// Start Caddy function
async function StartCaddy() {
  console.log("Start of StartCaddy function");
  const caddyAction = require("child_process").exec(`${process.resourcesPath}/Bin/caddy-start`, shellCallback);
  caddyAction.stdout.pipe(process.stdout)
  caddyAction.on("exit", () => console.log("Caddy action finished")) // This gets never executed
  await sleep(3000); // Wait 3 seconds
  console.log("Waited for 3 seconds now show localhost in new window");
  //windowURL = `http://127.0.0.1:2015/`;
  //mainWindow.close();
  //createWindow();
  // This only works if never used mainWindow.loadURL before???
  mainWindow.loadURL("http://127.0.0.1:2015/");
  //mainWindow.loadURL(`http://localhost:2015`);
  //mainWindow.reload();
  mainWindow.webContents.reloadIgnoringCache();
}

// Sleep function
function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

// Git action function
function GitAction() {
  const gitAction = require("child_process").exec(`${process.resourcesPath}/Bin/git-action`, shellCallback);
  gitAction.stdout.pipe(process.stdout)
  gitAction.on("exit", () => console.log("Git action finished"))
}

// ZIP action function
function ZipAction() {
  const zipAction = require("child_process").exec(`${process.resourcesPath}/Bin/zip-action`, shellCallback);
  zipAction.stdout.pipe(process.stdout)
  zipAction.on("exit", () => console.log("ZIP action finished"))
}

// Callback
function shellCallback(error, stdout, stderr) {
  //console.log(error, stdout)
  console.log("error: " + error)
  //console.log("stdout: " + stdout)
  //console.log("stderr: " + stderr)
}

// Execute bash script
//const exec = require("child_process").exec;
//exec("/Users/aronsommer/Documents/Xcode-Projects/CocoaWebView/Resources/caddy-start", shellCallback);

// Execute bash script and than do something if finished
// CAUTION!!! ${process.resourcesPath} does not work when yarn start, only works with app in dist folder
//const child = require("child_process").exec(`${process.resourcesPath}/Bin/test`, shellCallback);
//child.stdout.pipe(process.stdout)
//child.on("exit", () => console.log("guguuus fertig"))

// Load file from Resources folder
// CAUTION!!! ${process.resourcesPath} does not work when yarn start, only works with app in dist folder
//mainWindow.loadURL(`file:///${process.resourcesPath}/Content/index.html`);
//console.log(`file:///${process.resourcesPath}/Content/index.html`);


Solution 1:[1]

It looks like a scoping problem.

At the very end of your createWindow() function add the following line.

function createWindow() {
  ...

  return mainWindow; // <-- Add this line
}

In your app.whenReady() function edit the following line.

app.whenReady().then(() => {
  // windowURL = `file:///${process.resourcesPath}/Content/index.html`;

  mainWindow = createWindow(); // <-- Edit this line

If functions in your above code are seperated into their own files then you can do something similar as shown below.

main.js

const { app, BrowserWindow } = require('electron')

const appMainWindow = require('mainWindow');

let mainWindow;

app.whenReady().then(() => {
  mainWindow = appMainWindow.createWindow();

  // Re-activate Application when in macOS dock.
  app.on('activate', () => {  
    if (BrowserWindow.getAllWindows().length === 0) { appMainWindow.createWindow(); }
  })

})

mainWindow.js

let mainWindow;

function createWindow() {
  ...

  return mainWindow;
}

function getWindow() {
  return mainWindow;
}

module.exports = {createWindow, getWindow};

startCaddy.js

const appMainWindow = require('mainWindow');

function StartCaddy() {
  ...
  appMainWindow.getWindow().loadURL("http://127.0.0.1:2015/");
  ... 
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 midnight-coding