To solve the "ReferenceError require is not defined" error, remove the type property if it's set to module in your package. json file and rename any files that have a . mjs extension to have a . js extension.
Electron node integration refers to the ability of accessing Node. js resources from within the “renderer” thread (the UI). It is enabled by default in Quasar CLI, although Electron is encouraging developers to turn it off as a security precaution.
@electron/remote is an Electron module that bridges JavaScript objects from the main process to the renderer process. This lets you access main-process-only objects as if they were available in the renderer process.
As of version 5, the default for nodeIntegration
changed from true to false.
You can enable it when creating the Browser Window:
app.on('ready', () => {
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
}
});
});
I hope this answer gets some attention, because a large majority of answers here leave large security holes in your electron app. In fact this answer is essentially what you should be doing to use require()
in your electron apps. (There is just a new electron API that makes it a little bit cleaner in v7).
I wrote a detailed explanation/solution in github using the most current electron apis of how you can require()
something, but I'll explain briefly here why you should follow an approach using a preload script, contextBridge and ipc.
Electron apps are great because we get to use node, but this power is a double-edged sword. If we are not careful, we give someone access to node through our app, and with node a bad actor can corrupt your machine or delete your operating system files (among other things, I imagine).
As brought up by @raddevus in a comment, this is necessary when loading remote content. If your electron app is entirely offline/local, then you are probably okay simply turning on
nodeIntegration:true
. I still would, however, opt to keepnodeIntegration:false
to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using thenodeIntegration:true
attack vector (incredibly rare, but could happen)!
This problem manifests when you (any one of the below):
nodeIntegration:true
enabledremote
moduleAll of these problems give uninterrupted access to node from your renderer process. If your renderer process is ever hijacked, you can consider all is lost.
The solution is to not give the renderer direct access to node (ie. require()
), but to give our electron main process access to require
, and anytime our renderer process needs to use require
, marshal a request to the main process.
The way this works in the latest versions (7+) of Electron is on the renderer side we set up ipcRenderer bindings, and on the main side we set up ipcMain bindings. In the ipcMain bindings we set up listener methods that use modules we require()
. This is fine and well because our main process can require
all it wants.
We use the contextBridge to pass the ipcRenderer bindings to our app code (to use), and so when our app needs to use the require
d modules in main, it sends a message via IPC (inter-process-communication) and the main process runs some code, and we then send a message back with our result.
Roughly, here's what you want to do.
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));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.send("toMain", "some data");
</script>
</body>
</html>
I'm the author of secure-electron-template
, a secure template to build electron apps. I care about this topic, and have been working on this for a few weeks (at this point in time).
For security reasons, you should keep nodeIntegration: false
and use a preload script to expose just what you need from Node/Electron API to the renderer process (view) via window variable. From the Electron docs:
Preload scripts continue to have access to
require
and other Node.js features
main.js
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})
preload.js
const { remote } = require('electron');
let currWindow = remote.BrowserWindow.getFocusedWindow();
window.closeCurrentWindow = function(){
currWindow.close();
}
renderer.js
let closebtn = document.getElementById('closebtn');
closebtn.addEventListener('click', (e) => {
e.preventDefault();
window.closeCurrentWindow();
});
First off, @Sathiraumesh solution leaves your electron application with huge security issue. Imagine that your app is adding some extra features to messenger.com
, for example toolbar's icon will change or blink when you've have unread message. So in your main.js
file, you create new BrowserWindow like so (notice I intentionally misspelled messenger.com):
app.on('ready', () => {
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadURL(`https://messengre.com`);
});
What if messengre.com
is a malicious website, that wants to harm your computer. If you set nodeIntegration: true
this site has access to your local file system and can execute this:
require('child_process').exec('rm -r ~/');
And your home directory is gone.
Solution
Expose only what you need, instead of everything. This is achived by preloading javascript code with require
statements.
// main.js
app.on('ready', () => {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: `${__dirname}/preload.js`
}
});
mainWindow.loadURL(`https://messengre.com`);
});
// preload.js
window.ipcRenderer = require('electron').ipcRenderer;
// index.html
<script>
window.ipcRenderer.send('channel', data);
</script>
Now awful messengre.com
cannot delete your entire file system.
It looks like Electron's security evolved like this (source).
Electron 1 nodeIntegration defaults to true
Renderer has full access to Node API -- huge security risks if Renderer loads remote code.
Electron 5 nodeIntegration defaults to false
When set to false, a preload script is used to expose specific API to Renderer. (The preload script always has access to Node APIs regardless of the value of nodeIntegration)
//preload.js
window.api = {
deleteFile: f => require('fs').unlink(f)
}
Electron 5 contextIsolation defaults to true (actually still defaults to false in Electron 11)
This causes preload script to run in a separate context. You can no longer do window.api = ...
. You now have to do:
//preload.js
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('api', {
deleteFile: f => require('fs').unlink(f)
})
Electron 6 require()
ing node builtins in sandboxed renderers no longer implicitly loads the remote version
If Renderer has sandbox
set to true, you have to do:
//preload.js
const { contextBridge, remote } = require('electron')
contextBridge.exposeInMainWorld('api', {
deleteFile: f => remote.require('fs').unlink(f)
})
Electron 10 enableRemoteModule default to false (remote module deprecated in Electron 12)
The remote
module is used when you need to access Node APIs from a sandboxed Renderer (as in above example); or when you need to access Electron APIs that are available only to the Main process (such as dialog, menu). Without remote
, you'll need to write explicit IPC handlers like follows.
//preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
displayMessage: text => ipcRenderer.invoke("displayMessage", text)
})
//main.js
const { ipcMain, dialog } = require('electron')
ipcMain.handle("displayMessage", text => dialog.showMessageBox(text))
Electron 10 deprecate nodeIntegration flag (removed in Electron 12)
Always set {nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}
.
For max security, set {sandbox: true}
. Your preload script will have to use IPC to call the Main process to do everything.
If sandbox
is false, your preload script can access Node API directly, as in require('fs').readFile
. You're secure as long as you don't this:
//bad
contextBridge.exposeInMainWorld('api', {
readFile: require('fs').readFile
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With