Electron: How to share BrowserWindow instance across multiple files?

Viewed 270

I want to have a js file let's say window.js that is responsible for creating and exporting an unique instance of BrowserWindow so that I can reuse this instance across multiple js files.

Until now I tried this:

    const { app, BrowserWindow } = require("electron");
    
    let window = null;
   
    const createWindow = () => {
      if (window) return;
      window = new BrowserWindow({
      minWidth: 820,
      minHeight: 620,
      width: 820,
      height: 620,
      resizable: false,
      webPreferences: {
        preload: path.join(__dirname, "preload.js"),
        enableRemoteModule: true,
      },
     });
     window.removeMenu();
    };

app.whenReady().then(() => {
  createWindow();
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
      //   showRecorderWindow();
    }
  });
});

module.exports = {
  window
}

However when I require the window using: const { window } = require("./window.js"); the window variable is always null. Is there any way to achieve this?

2 Answers

The use of "setters" and "getters" will resolve you problem.

Your main.js file should require your window.js module and create() the window.

If any other Javascript file(s) requires access to the window instance then within that file you can require the window.js module and then just call the get() function.


Within your main.js (main thread) file:

  1. require the window.js file which contains the create and get functions.
  2. Declare the window variable so it is not garbage collected.
  3. On electronApp.on('ready', ...) allocate to the window variable the created window instance.
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;

const nodePath = require("path");

const appWindow = require(nodePath.join(__dirname, 'window'));

// Prevent garbage collection
let window;

electronApp.on('ready', () => {
    window = appWindow.create();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        appWindow.create();
    }
});

Within your window.js (main thread) file:

  1. Create 2 functions - One to create the window and one to get the window instance.
  2. Export both functions for use elsewhere.
const electronBrowserWindow = require('electron').BrowserWindow;

const nodePath = require('path');

let window; // Top level scope within the module

function create() {
    window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window; // Return the instance of the window
}

function get() {
    return window; // Return the instance of the window
}

// Export the publicly available functions.
module.exports = {create, get};

And finally, when you need to refer to the instance of window in another.js (main thread) file:

  1. require the window.js file.
  2. Call the get() function.
const nodePath = require('path');

const appWindow = require(nodePath.join(__dirname, 'window'));

let window = appWindow.get();

This is really handy when you need to use a specific window as a parent to a child window or dialog.

When you use module.exports = <object>, you are creating a copy of the object with no link back to the original object. In your code, the module.exports section is run before the window is created in the app.whenReady() section. This is because your function in the app.whenReady() section is run asynchronously. So at the time module.exports = { window } is run, window still equals null. To fix this you need to move module.exports into app.whenReady() after createWindow() or, to fix the scoping, into createWindow() like so (I also corrected the indentation):

const { app, BrowserWindow } = require("electron");

const createWindow = () => {
  if (window) return;
  const window = new BrowserWindow({
    minWidth: 820,
    minHeight: 620,
    width: 820,
    height: 620,
    resizable: false,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      enableRemoteModule: true,
    },
  });
  window.removeMenu();
  module.exports = {
    window
  }
};

app.whenReady().then(() => {
  createWindow(); 
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
      //   showRecorderWindow();
    }
  });
});

Now when you require windows.js it will return the latest window object. Just make sure to do your const { window } = require("./window.js"); once the app is ready:

// file2.js

const { app, BrowserWindow } = require("electron");

let window = null;

app.whenReady().then(() => {
  app.on("activate", () => {
    if (window == null) {
      { window } = require("./window.js");
    }
  });
});

Note that you can't require("./window.js") multiple times to get the latest window as node.js will hold a copy of window.js when you require it. You may also want to look into inter-process communication: https://www.electronjs.org/docs/latest/tutorial/ipc

Related