1

In Electron, how do I enable NodeJS inside my React Code?

What are the steps I need to take? I'm trying to access the file system component which is normally accessed in node using:

const fs = require('fs');

Inside my React code this didn't compile, so I tried changing it to import and it also did not work.

import fs from 'fs';
// Gives error
Module not found: Error: Can't resolve 'fs' ...

Searching for a solution I found some questions but not an answer (yet):

I saw a SO question/answer that matched this but now I can't find it!

I've already enabled NodeJS in my Electron main app using:

  mainWindow = new BrowserWindow({
    width: winState.width,
    height: winState.height,
    x: winState.x,
    y: winState.y,
    webPreferences: {
      // --- !! ANT !! ---
      // Disable 'contextIsolation' to allow 'nodeIntegration'
      // 'contextIsolation' defaults to "true" as from Electron v12
      contextIsolation: false,
      nodeIntegration: true, /* Allow NodeJS code to be called in Web Pages */
      enableRemoteModule: true
    }
  })

PatS
  • 8,833
  • 12
  • 57
  • 100

2 Answers2

1

For filesystem access inside an Electron app with React, you will need three different sections of your codebase:

  • The main process, which runs in Node and has access to fs and everything else Node has. This may eventually call new BrowserWindow, which opens a window and runs the preload script.

  • The preload script is meant to manage communication between the main process and the browser. While it can do more, it really, really shouldn't; it's best for it to act only as a bridge. Your preload script may be as simple as something like

    contextBridge.exposeInMainWorld('electronAPI', {
      sendMessageToMainProcess: (channel, payload) => ipcRenderer.invoke(channel, payload),
    })
    
  • Your page script, which runs the React code (and any other code that runs in the window). This runs in a context similar to a browser and doesn't have access to any Node functions. In order to dynamically read from the filesystem from the page, the page will need to send a message to the main process (over the API exposed by the preload script), and then the main process will need to listen for messages, run whatever fs operations are needed, and then reply to the message with the result. The electronAPI example above can be invoked from React with window.electronAPI.sendMessageToMainProcess(channel, payload). The main process can listen for messages (attached via the preload script) and reply to them with, for example

    ipcMain.handle('someChannelName', (event, payload) => {
      // do verification here if desired...
      return fs.promises.readFile('somepath', 'utf-8')
        // .catch(handleErrors);
    });
    

    By returning a Promise from the handler function, the value that the Promise resolves to will be sent back to the caller.

The above is pretty easy to set up and doesn't require the unsafe contextIsolation: false nor nodeintegration: true:

Context isolation has been enabled by default since Electron 12, and it is a recommended security setting for all applications.

If any of the code in the page script is not personally your own (for example, if you use an external library or few - such as React), then you should not allow the page script direct access to Node or your filesystem.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thanks for the detailed answer. I'm converting a simple html/javascript app to react, and used the approach you've detailed. It reminded me that I could do the same thing with React! I found a video https://youtu.be/oAaS9ix8pes?t=272 that showed how to get past my roadblock; which was using `const fs = window.require('fs');`, but will convert it to your solution ASAP (to avoid the security issues you point out). – PatS Jan 09 '23 at 14:40
0

In an Electron app, the way to use the NodeJS require function is to use window.require as shown below:

const fs = window.require('fs');

This only works if you've first enabled nodeIntegration: true when creating the BrowserWindow.

mainWindow = new BrowserWindow({
      nodeIntegration: true, 
});

As @CertainPerformance, says this is really a hack. I used it when first getting started because it was easier than learning how to do the equivalent functionality via the Electron IPC mechanism (ipcRenderer.invoke)

PatS
  • 8,833
  • 12
  • 57
  • 100