When I try to use fs and path modules I am getting the below error: Please help
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
I ran into the same issue, fixed it by adding this to .erb/webpack.config.base.ts :
resolve: {
fallback: {
fs: false,
tls: false,
net: false,
path: false,
zlib: false,
http: false,
https: false,
stream: false,
crypto: false,
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
modules: [webpackPaths.srcPath, 'node_modules'],
More information on this thread : https://stackoverflow.com/a/64802753
Hope that will help.
More precisely they blocked the system library for security reasons in the new versions of Electron.
you can unblock them as @RPruvost suggests ( I didn't try this way)
You have to implement the code outside the web app (src/main) and use the IPC (inter-process communication) to request the main application to execute the related commands. Still, it is very tedious and prone to async-related problems.
__dirname is a Node.js global, you cannot use it without access to the Node.js APIs:
https://nodejs.org/docs/latest/api/globals.html#globals_dirname
Try to add this to your main file (src/main/main.ts), in mainWindow:
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
@RPruvost I tried your resolve: { fallback: {...
code above, and now I get __dirname is not defined
.
However, adding nodeIntegration: true
in main.ts
did nothing, I still get the __dirname is not defined
.
In my case:
I am trying to import { clipboard } from 'electron'
in the renderer in App.tsx
.
The moment that I let cb = clipboard;
then I get the dreaded Can't resolve 'fs' in '/path/to/node_modules/electron'
.
And then when I add the fallback
code to .erb/config/webpack.config.base.ts
, I then get __dirname is not defined
.
Ideas?
Code's here,
UPDATE: I got past my issue by avoiding it (access the clipboard in main instead). For future people, I got electron
clipboard
to work by calling it using ipcRenderer
from the renderer
to main
. So in main.tsx
I added import { clipboard } from 'electron';
and a handler to expose the clipboard. I suspect that having the ipcRenderer
in the renderer
was confounding my effort above.
@dmatora same
I got past my own issue (using the clipboard
in renderer
) by avoiding it (access the clipboard
in main
instead). Electron Clipboard documentation makes it seem like it should work in both renderer and in main, but I found that not to be true, at least while I had ipc
in the renderer. So, For future people, I got electron
clipboard
to work by calling it using ipcRenderer
from the renderer
to main
's process. So in main.tsx
I added import { clipboard } from 'electron';
and a handler to expose the clipboard
. I suspect that having the ipcRenderer
in the renderer
was confounding my effort above.
Doing the necessary IPC communication solved the issue for me. I implemented functionality that requires fs modules on the main thread and sends the response back to the renderer process. It is completely restricted in the latest version of the electron react boilerplate. Thank you for your help.
I want to know how you do to resolve it?
Doing the necessary IPC communication solved the issue for me. I implemented functionality that requires fs modules on the main thread and sends the response back to the rendered process. It is completely restricted in the latest version of the electron react boilerplate. Thank you for your help.
I want to know how you do to resolve it?
There is a section in the docs if you want an example https://www.electronjs.org/docs/latest/tutorial/ipc
That said...
First you have to understand the components of Electron(the name are not exact but is more to be able to talk):
Main Thread (that run the browser and interface with the system)
PreLoad (security layer to handle the communication between WebApp and main thread)
Web Application (the be app with your UI)
System related libraries (libraries that works with node.js for example) can only be loaded in the main thread, and not in the WebApp. The PreLoad is simply an isolation techniques to improve security, that manage the communication of the WebApp with the Main Thread.
To use the IPC(inter-Process Comunication) you need to do 3 things send the request (like you are talking with a server), ass the request to the preload (only to restrict the scope for security) handle the request on the main thread.
SO let's take the example of the docs to sent a request to change the name showed on the browser window:
**1: Create the call in the preload to enable the front-end to talk with the backed:
const { contextBridge, ipcRenderer } = require('electron')
// we tell the context bridge that we want to register a function under the browser window key of ElectronAPI,
// this function will be callable from the browser, in this case the function is called **setTitle** that will send to the main process a request with ID _'set-title'_ and payload _'title'_
// Note that you can change the ElectronAPI with what you want but then you need to use window.your_choice.function, when you use it in the webApp!
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
2: Now we can call this exposed function from the WebApp, I'm using here a simple js script that we can load in the main.html, but you can use it in angular or react components too, since the function is registered in the browser's window object :
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
// Call the function registered in the window under 'ElectronAPI', as said you can change this by chagning the preload file
// if you want to use something different from 'ElectronAPI'
window.electronAPI.setTitle(title)
3: Main thread handle the request:
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
// IPC request handling the IPC message is composed by a request ID('set-title') and a payload if needed (here is the title)
// you need to register it in the window creation
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
mainWindow.loadFile('index.html')
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
Hope it helps!
Doing the necessary IPC communication solved the issue for me. I implemented functionality that requires fs modules on the main thread and sends the response back to the rendered process. It is completely restricted in the latest version of the electron react boilerplate. Thank you for your help.
I want to know how you do to resolve it?
There is a section in the docs if you want an example https://www.electronjs.org/docs/latest/tutorial/ipc
That said... First you have to understand the components of Electron(the name are not exact but is more to be able to talk):
Main Thread (that run the browser and interface with the system)
PreLoad (security layer to handle the communication between WebApp and main thread)
Web Application (the be app with your UI)
System related libraries (libraries that works with node.js for example) can only be loaded in the main thread, and not in the WebApp. The PreLoad is simply an isolation techniques to improve security, that manage the communication of the WebApp with the Main Thread.
To use the IPC(inter-Process Comunication) you need to do 3 things send the request (like you are talking with a server), ass the request to the preload (only to restrict the scope for security) handle the request on the main thread.
SO let's take the example of the docs to sent a request to change the name showed on the browser window: **1: Create the call in the preload to enable the front-end to talk with the backed:
const { contextBridge, ipcRenderer } = require('electron')
// we tell the context bridge that we want to register a function under the browser window key of ElectronAPI,
// this function will be callable from the browser, in this case the function is called **setTitle** that will send to the main process a request with ID _'set-title'_ and payload _'title'_
// Note that you can change the ElectronAPI with what you want but then you need to use window.your_choice.function, when you use it in the webApp!
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
2: Now we can call this exposed function from the WebApp, I'm using here a simple js script that we can load in the main.html, but you can use it in angular or react components too, since the function is registered in the browser's window object :
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
// Call the function registered in the window under 'ElectronAPI', as said you can change this by chagning the preload file
// if you want to use something different from 'ElectronAPI'
window.electronAPI.setTitle(title)
3: Main thread handle the request:
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
// IPC request handling the IPC message is composed by a request ID('set-title') and a payload if needed (here is the title)
// you need to register it in the window creation
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
mainWindow.loadFile('index.html')
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
Hope it helps!
extremely grateful!
Hope it helps!
Thanks for the tutorial! To add onto this, I was getting this error: Property 'electronAPI' does not exist on type 'Window & typeof globalThis'. Did you mean 'electron'?
on the following line: window.electronAPI.setTitle();
To make this work, I had to add a renderer.d.ts
file:
export interface IElectronAPI {
setTitle: () => Promise<void>;
declare global {
interface Window {
electronAPI: IElectronAPI;
If anyone needs this, here is what works well in my local.
just a case for me and not promising it will fix your problem.
add handle function in your main.js like below:
async function handleFileOpen(type, arg) {
return await dialog(null, { properties:[], ...arg});
ipcMain.handle('dialog:showOpenDialog', async (event, arg) => {
return await handleFileOpen('showOpenDialog', arg);
this is how you use node api/function which you need in main process
3. expose them to renderer in preload.js using contextBridge:
const handler = {
dialog: {
showOpenDialog:(options) => {
return ipcRenderer.invoke('dialog:showOpenDialog', options);
contextBridge.exposeInMainWorld('electron', handler);
export type ElectronHandler = typeof handler;
in your code to use it:
window.electron.dialog.showOpenDialog({properties: [xxx]}).then();
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
modules: [webpackPaths.srcPath, 'node_modules'],
The Can't resolve 'fs'
error is gone but when I import fs from 'fs'; console.log(fs)
it shows undefined
.