If you want to read a file in Electron, you have to understand the various parts of an Electron app. In short, there is a main process and a renderer process. The main process has and is given all control to use node modules, such as the fs module that can read files. The renderer process should not have access to the fs module, but instead anytime it needs to use the fs module, it should ask the main process to use fs, and then return the result.
FYI the renderer process is the web page, the visible part of your Electron app.
This communication is called IPC (inter-process communication). The flow is:
- Renderer process sends a message to the main process over IPC
- The main process hears the message, and then reads a file with
fs
- The contents/result are sent back via IPC to the renderer process
- The renderer process now can do what it wants with the data from the file
A very rough example is below.
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
// Called when message received from main process
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
// Send a message to the main process
window.api.send("toMain", "some data");
</script>
</body>
</html>
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
fs.readFile("path/to/file", (error, data) => {
// Do something with file contents
// Send result back to renderer process
win.webContents.send("fromMain", responseObj);
});
});
preload.js
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
DISCLAIMER: I'm the author of a popular secure electron template, and have wrote a specific guide on how to use fs to read a file in an Electron app. I hope you give it a read because it has additional information there.