Recently I started practising javascript with the Electron framework but i've encountered a problem with the preload.js functionality. Maybe i didn't understand how to use it, but i can't require some of the electron constants because it returns undefined
My preload2.js:
const { BrowserWindow } = require("electron");
console.log(BrowserWindow);
In my window's console it returns
Undefined
I excluded the problem could be that my preload is not correctly executed but it may be:
My main.js:
const { app, BrowserWindow } = require('electron');
function createWindow() {
console.log(app.getAppPath());
const win = new BrowserWindow({
width: 1000,
height: 600,
//Web preferences può utilizzare il preload.js
webPreferences: {
preload: app.getAppPath() + "\\preload2.js",
}
});
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
Please help me i'm losing my mind
The proper use of Electron's preload.js
script is hard to grasp without a basic well-structured example. Many examples on the internet blend the primary function of the preload script (which is to manage the channels of communication between the main thread and render threads) with implementation of those channels.
To further your knowledge, have a good read of Electron's Process Model, Context Isolation and Inter-Process Communication pages.
Once you have read these pages (but not necessarily understood them) let's begin stitching them together so they all work as a whole.
As described earlier, the purpose of the preload.js
script is to communicate between the main thread and render thread(s). Preload scripts can become overwhelming very quickly if you add a large number of functions to them.
As an alternative approach, I use my (one and only) preload.js
script for management of IPC's (by channel name) only. What this means is that I specific whitelisted channel names that are transmitted or received between the main thread and render thread(s). Any channel names used that are not on the whitelist are denied passage. You can also send data when transmitting the channel name so the data can be received at the other end.
Ok, let's look at the preload.js
file.
preload.js
(main thread)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:fromRender'
],
// From main to render.
'receive': [
'message:toRender'
],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
In the above code you will see that the channel names message:fromRender
and message:toRender
have been added to the whitelist.
Note: Objects such as
BrowserWindow
cannot be sent via IPC as they are not serialisable. See Object serialization for more information.
Now, in your main.js
file, everything looks good except for the path to the preload script. Let's change that.
main.js
(main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
let win;
function createWindow() {
const win = new electronBrowserWindow({
x: 0,
y: 0,
width: 1000,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
win.loadFile('index.html')
.then(() => { win.show(); })
// Send message to (win) render thread
.then(() => { win.webContents.send('message:toRender', 'Hello from the main thread.' )};
return win;
}
electronApp.on('ready', () => {
win = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Listen for message from (any) render thread.
electronIpcMain.on('message:fromRender', (event, message) => {
console.log(message);
})
You will notice I have added 2 new sections of code. One to send a message to the render thread and one to receive a specific message from the render thread.
Finally, let's add functionality to our basic index.html
file.
index.html
(render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<label for ="button">Send message to main thread: </label>
<input type="button" id="button" value="Send">
</body>
<script>
// Listen for message from the main thread
window.ipcRender.receive('message:toRender', (message) => {
console.log(message);
});
document.getElementById('button').addEventListener('click', () => {
// Send a message to the main thread
window.ipcRender.send('message:fromRender', 'Hello from the render thread.');
});
</script>
</html>
Upon starting the application, the render thread console should display Hello from the main thread
.
Upon clicking the Send
button, the main thread should display Hello from the render thread
.
If you need any clarification or further explanation just let me know.