Options 2 and 4 gives you code seperation and prevents the UI from being blocked during a signin process.
In the render thread, seperating your javascript from your html is common practice as it seperates languages. If you were to place a substantial amount of javascript between <script>
tags in your html file then your html file could become difficulty to navigate, especially if more js code is added. Splitting apart code into their own responsibilities and including them with <script>
tags when needed would make you code much easier to navigate and read. Additionally, there is no performance hit like there is over the wire when including multiple <script>
tags in your html as they are all locally served files packaged within your Electron app.
Through the use of Inter-Process Communication and Context Isolation you can create an app that clearly defines main and render thread functionality. You will want to have a clear understanding of ipcMain and ipcRender
Render thread performance is upheld when its primary responsibily is painting, DOM updating and render side event management. Including cycle hungry functionality like requests or hitting the db will slow down or possibly block any UI interaction or updating. Therefore, functionality such as that should ideally be processed in the main thread.
What I would do is have 2 signin scripts. One in the main thread to handle the heavy processes like touching the json
file, etc and one in the render thread to handle the button event, feedback (EG: A spinner whilst signing in) and any errors (EG: json
file is missing).
I believe interacting with the json
file should be done from the main thread and not the render thread.
I hope the below code helps you a tremendous amount as it took me some time refactoring my own code to work out a simple, logical structure like shown below. PS: Add some directory structure as well to make it easier to navigate whilst coding.
index.html
(render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</ttile>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="script.js">
</head>
<body>
<input type="text" id="username" name="username">
<input type="password" id="passowrd" name="password">
<input type="button" id="signin" value="Sign-In">
<div id="signin_error"><div>
...
<body>
</html>
script.js
(render thread)
let signinButton;
let signinError;
(function() {
username = document.getElementById('username');
password = document.getElementById('password');
signinError = document.getElementById('signin_error');
signinButton.addEventListener('click', () => {
signin({
username: username.value,
password: password.value
);
})
window.ipcRender.receive('signin:error', (message) => {
signinError(message);
});
})();
function signin(details) {
signinButton.classList.add('spinner');
window.ipcRender.send('signin:signin', details);
}
function signinError(message) {
signinButton.classList.remove('spinner');
signinError.innerText = message;
}
preload.js
(main thread)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send: [
'signin:signin'
],
// From main to render.
'receive': [
'signin:error'
],
// From render to main and back again.
'sendReceive': []
}
};
contextBridge.exposeInMainWorld(
'ipcRender',
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRender.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
ipcRender.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);
}
}
}
};
mainWindow.js
(main thread)
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require('path');
let mainWindow;
function create() {
mainWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
worldSafeExecuteJavaScript: true,
enableRemoteModule: false,
preload: nodePath.join(__dirname, 'preload.js')
}
});
});
mainWindow.loadFile(nodePath.join(__dirname, 'main.html'))
.then(() => { mainWindow.show(); })
}
function get() {
return mainWindow;
}
module.exports = {create, get}'
main.js
(main thread - entry point)
const electronApp = require('electron').app;
const nodePath = require('path');
let appMainWindow = require(nodePath(__dirname, 'mainWindow.js'));
require(nodePath.join(__dirname, 'signin.js'));
let mainWindow = null;
(function() {
// Application is now ready to start.
electronApp.on('ready', () => {
mainWindow = appMainWindow.create();
// Re-activate Application when in macOS dock.
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
appMainWindow.create(mainWindow);
}
});
// Close Application completely if not on macOS.
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
})();
signin.js
(main thread)
const electronIpcMain = require('electron').ipcMain;
let appMainWindow = require(nodePath(__dirname, 'mainWindow.js'));
(function() {
electronIpcMain.on('signin:signin', (event, details) => { signin(details); })
})();
function signin(details) {
// Try signing in.
console.log(details);
// If successful, update json file and redirect to a new page.
// If there is an error.
appMainWindow.get().webContents.send('signin:error', 'There was an error signin in.'})
}