Learn more about Russian war crimes in Ukraine.

How does require work in Electron?

Electron apps copy the Chromium process architecture. When you start an app with something like electron main.js, this starts the Chromium browser process. You pass it a Node.js module like main.js. Node.js uses the CommonJS system, so the initial module can require further modules. For example, this script will act much like a Node.js script:

const { readFileSync } = require('fs');

const {getValue, increment} = require('./counter.js');

The additional power of Electron comes from require('electron'). This module provides an API for launching new Renderer processes by running things like:

const { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
  const window = new BrowserWindow();

The Renderer processes act much like ordinary web pages. But you can allow them to use the Node.js module system, too! To do so, we pass:

const window = new BrowserWindow({ 
  webPreferences: { 
    nodeIntegration: true 

With nodeIntegration: true set, require becomes available to JavaScript in the context of that page, and we can write things like:

<!doctype html>
      const electron = require('electron');

The Node.js module system is stateful. For example, we can have a counter.js module like:

let counter = 0;
exports.getValue = () => counter;
exports.increment = () => counter++;

In a typical Node.js app, this module would be loaded once, and the counter state would be shared globally. This is not the case in Electron. Each Renderer process is isolated, and its modules will have their own state. In other words, you can’t use modules for sneaky inter-process communication (we have other things for that).

The require('electron') module is a built-in, but we can require local modules in the file system too. As far as I can tell, Electron searches for modules in the same way Node.js does. But relative to what starting filepath?

If you use loadFile, modules seem to be resolved relative to the loaded file. For example, if you loadFile("foo/bar/baz.html"), then a call to require('some_module)' on that page will look for a module at foo/bar/baz/node_modules/some_module. But if you use loadURL with a non-file protocol, require('some_module') won’t search for modules on disk; it seems to only work for built-in modules like "electron" or "fs". This is a good thing (and you probably shouldn’t be using nodeIntegration with loadURL anyway!).

Even though we can use require to load our dependencies in Renderer processes, I’m not sure we should. I feel like it’s better to stick to the standard ways to load dependencies in a browser, like <script> and import.

(Although using import in a Renderer process is probably another can of worms. It’s ambiguous: does it use the browser’s module resolution, or that from Node.js? Another post for another time.)

What can computers do? What are the limits of mathematics? And just how busy can a busy beaver be? This year, I’m writing Busy Beavers, a unique interactive book on computability theory. You and I will take a practical and modern approach to answering these questions — or at least learning why some questions are unanswerable!

It’s only $19, and you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

After months of secret toil, I and Andrew Carr released Everyday Data Science, a unique interactive online course! You’ll make the perfect glass of lemonade using Thompson sampling. You’ll lose weight with differential equations. And you might just qualify for the Olympics with a bit of statistics!

It’s $29, but you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

More by Jim

Tagged #programming, #electron, #javascript. All content copyright James Fisher 2020. This post is not associated with my employer. Found an error? Edit this page.