Windows and the renderer

Create the main window

Configure a BrowserWindow with a preload script and load the Webpack renderer entry produced by Electron Forge.

Windows are created in the main process, after Electron is ready. Let’s replace the generated window function in src/index.js with our own:

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

let mainWindow = null

function createWindow() {
  const win = new BrowserWindow({
    width: 960,
    height: 700,
    minWidth: 720,
    minHeight: 500,
    show: false,
    webPreferences: {
      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true
    }
  })

  mainWindow = win
  win.on('closed', () => {
    if (mainWindow === win) mainWindow = null
  })

  win.once('ready-to-show', () => win.show())
  win.loadURL(MAIN_WINDOW_WEBPACK_ENTRY)

  return win
}

The two uppercase constants come from the Forge Webpack plugin, as we saw in the previous lesson. One points at the renderer page, the other at the bundled preload script.

We keep a reference to the window in mainWindow. Later, IPC handlers and the menu need to know which window is the trusted one. When the window closes we set the reference back to null, so nothing tries to use a destroyed window.

Show the window when it’s ready

Notice show: false. Without it, Electron shows the window immediately, and the user sees a blank white rectangle for a moment while the page loads.

With show: false, the window stays hidden. The ready-to-show event fires after the first useful paint, and that’s when we call win.show(). The result is a window that appears already drawn.

If your interface renders slowly, delaying the whole window can feel worse than a flash. In that case a matching backgroundColor option is often the better choice. For Desktop Notes the page is tiny, so ready-to-show is fine.

The three security options

These three settings are the modern Electron defaults. I still write them explicitly, so nobody has to remember what the default is:

  • nodeIntegration: false keeps Node.js out of page code
  • contextIsolation: true separates the preload globals from the page globals
  • sandbox: true runs the renderer in Chromium’s sandbox, with fewer OS privileges

At some point you’ll hit a preload script that can’t require a Node.js module, and a search result will tell you to set sandbox: false. Don’t. A sandboxed preload has a limited environment by design. Move the privileged work into the main process and expose one IPC method for it. That’s what we do in the next module.

Check it

Run the app and open DevTools. Type require in the console. It should be undefined, because the page has no Node.js.

The window should still load and show the template page. That works because the renderer bundle contains only browser-compatible code. If you see a blank window instead, check the terminal: a wrong preload path is the usual cause.

Lesson completed