I created an App using electron that interacts with local files using IPC. When i run it using npm start
, éverything works as excpected, but when i try to package the app into an .exe
file using the steps provided in the docs (https://www.electronjs.org/docs/latest/tutorial/tutorial-packaging), the App wont start and i get this error:
Error
× A JavaScript error occurred in the main process
Uncaught Exception:
X
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file and data are supported by the default ESM loader. Received protocol 'electron:'
at getSourceSync (node:internal/modules/esm/load:74:11)
at defaultLoadSync (node:internal/modules/esm/load:172:32)
at ModuleLoader.getModuleJobForRequire (node:internal/modules/esm/loader:410:24)
at new ModuleJobSync (node:internal/modules/esm/module_job:341:34)
at
ModuleLoader.importSyncForRequire (node:internal/modules/esm/loader:357:11)
at loadESMFromCJS (node:internal/modules/cjs/loader:1392:24)
at Module._compile (node:internal/modules/cjs/loader: 1543:5)
at Module._extensions..js (node:internal/modules/cjs/loader:1722:10)
at Module.load (node:internal/modules/cjs/loader: 1296:32)
at Module._load (node:internal/modules/cjs/loader:1115:12)
OK
Electron version: 35.0.1
Node version: v22.14.0
I suspect it has to do something with the way i handle paths, so here are some example codes
// main.js
const { app, BrowserWindow, dialog, ipcMain, shell} = require('electron');
const path = require('path');
const fs = require('fs');
const pdf = require('pdf-parse');
const ElectronStore = require('electron-store');
const store = new ElectronStore.default();
function createWindow() {
let win = new BrowserWindow({
width: 1200,
height: 700,
icon: path.join(__dirname,'assets/icon.png'),
webPreferences: {
nodeIntegration: false, // Security best practice
contextIsolation: true, // Isolate main and renderer processes
enableRemoteModule: false,
devTools: true,
preload: path.join(__dirname, 'preload.js') // Use a preload script
}
});
// win.removeMenu()
win.loadFile(path.join(__dirname, 'index.html'));
}
ipcMain.handle("fs:readdir",(event, path) =>{
return fs.promises.readdir(path)
});
ipcMain.handle("fs:isDir", (event, filePath, document) =>{
return fs.lstatSync(path.join(filePath,document)).isDirectory()
});
//preload.js
contextBridge.exposeInMainWorld('fs',{
readdir: (path) => ipcRenderer.invoke("fs:readdir",path),
isDir: (path, document) => ipcRenderer.invoke("fs:isDir",path,document)
})
I already updated my Node.js version
As I noted in the comments I was having the same issue as you. I removed my includes one-by-one until I narrowed it down to electron-store
. I don't think it's compatible with require()
syntax when bundled for some reason (yet, it works in dev mode).
I found this gist which describes the problem: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
The package that linked you here is now pure ESM. It cannot be
require()
'd from CommonJS.This means you have the following choices:
- Use ESM yourself. (preferred)
Useimport foo from 'foo'
instead ofconst foo = require('foo')
to import the package. You also need to put"type": "module"
in your package.json and more. Follow the below guide.- If the package is used in an async context, you could use
await import(…)
from CommonJS instead ofrequire(…)
.- Stay on the existing version of the package until you can move to ESM.
- Since Node.js 22, you may be able to
require()
ESM modules. However, I strongly recommend moving to ESM instead.You also need to make sure you're on the latest minor version of Node.js. At minimum Node.js 16.
I would strongly recommend moving to ESM. ESM can still import CommonJS packages, but CommonJS packages cannot import ESM packages synchronously.
So, stubborn devs lol. I fixed this by doing:
"electron-store": "v8.2.0"
in my package.json
and reinstalling the packages. 8.2.0 seems to be the last release before 9.0.0 which forces ESM modules.
Hope this helps.