electron-re
Test on electron@8.2.0 / 9.3.5
I ) What can be used for
- In Electron Project
Using electron-re
to generate some service processs and communicate between main process
,render process
and service
. In some Best Practices
of electron tutorials, it suggests to put your code that occupying cpu into rendering process instead of in main process, exactly you can use it for. Check usage of Servcie
and MessageChannel
below.
- In Nodejs/Electron Project
Besides, If you want to create some sub processes (see nodejs child_process
) that not depends on electron runtime
, there is a process-pool written for pure nodejs runtime
and and can be used in electron/nodejs both. Check usage of ChildProcessPool
and ProcessHost
below, simple and flexible.
II ) Install
$: npm install @nojsja/electron-re --save
$: yarn add @nojsja/electron-re
$: npm install electron-re --save
$: yarn add electron-re
III ) Instruction 1: Service
working with MessageChannel, remember to check usage "Instruction 2".
1. The arguments to create a service
The service
process is a customized render process that works in the background, receiving path
, options
as arguments:
- path -- the absolute path to a js file
const { BrowserService } = require('electron');
const myServcie = new BrowserService('app', path.join(__dirname, 'path/to/app.service.js'));
- options -- the same as
new BrowserWindow()
options
const myService = new BrowserService('app', 'path/to/app.service.js', options);
2. Enable service auto reload after code changed
The auto-reload
feature is based on nodejs - fs.watch
api, When webSecurity closed and in dev
mode, service will reload after the code of service changed.
{
...
scripts: {
start: 'cross-env NODE_ENV=dev electron index.js',
start: 'cross-env NODE_ENV=development electron index.js',
}
...
}
global.nodeEnv = 'dev';
const myService = new BrowserService('app', 'path/to/app.service.js', {
...options,
webPreferences: { webSecurity: false }
});
3. The methods of a service instance
The service instance is a customized BrowserWindow
instance too, initialized by a file worked with commonJs
module, so you can use require('name')
and can't use import some from 'name'
syntax. It has two extension methods:
suggest to put some business-related code into a service.
const {
BrowserService,
MessageChannel
} = require('electron-re');
...
app.whenReady().then(async() => {
const myService = new BrowserService('app', 'path/to/app.service.js');
await myService.connected();
mhyService.openDevTools();
mhyService.webContents.send('channel1', { value: 'test1' });
});
...
const { ipcRenderer } = require('electron');
ipcRenderer.on('channel1', (event, result) => {
...
});
IV ) Instruction 2: MessageChannel
working with Service
When sending data from main/other process to a service you need to use MesssageChannel
, such as: MessageChannel.send('service-name', 'channel', 'params')
, And also it can be used to replace other build-in ipc
methods, more flexible.
1. Confirm to require it in main process entry first
in main process:
const {
BrowserService,
MessageChannel
} = require('electron-re');
const isInDev = process.env.NODE_ENV === 'dev';
...
app.whenReady().then(() => {
const myService = new BrowserService('app', 'path/to/app.service.js');
myService.connected().then(() => {
if (isInDev) myService.openDevTools();
MessageChannel.send('app', 'channel1', { value: 'test1' });
MessageChannel.invoke('app', 'channel2', { value: 'test1' }).then((response) => {
console.log(response);
});
MessageChannel.on('channel3', (event, response) => {
console.log(response);
});
MessageChannel.handle('channel4', (event, response) => {
console.log(response);
return { res: 'channel4-res' };
});
})
});
2. Use it to send/receive data in a service named app
in a service process named app:
const { ipcRenderer } = require('electron');
const { MessageChannel } = require('electron-re');
MessageChannel.on('channel1', (event, result) => {
console.log(result);
});
MessageChannel.handle('channel2', (event, result) => {
console.log(result);
return { response: 'channel2-response' }
});
MessageChannel.invoke('app2', 'channel3', { value: 'channel3' }).then((event, result) => {
console.log(result);
});
MessageChannel.send('app', 'channel4', { value: 'channel4' });
3. Use it to send/receive data in an another service named app2
MessageChannel.handle('channel3', (event, result) => {
console.log(result);
return { response: 'channel3-response' }
});
MessageChannel.once('channel4', (event, result) => {
console.log(result);
});
MessageChannel.send('main', 'channel3', { value: 'channel3' });
MessageChannel.invoke('main', 'channel4', { value: 'channel4' });
4. Use it to send/receive data in a render process window
const { ipcRenderer } = require('electron');
const { MessageChannel } = require('electron-re');
MessageChannel.send('app', ....);
MessageChannel.invoke('app', ....);
MessageChannel.send('main', ....);
MessageChannel.invoke('main', ....);
V ) Instruction 3: ChildProcessPool
working with ProcessHost, remember to check usage "Instruction 4".
Multi-process helps to make full use of multi-core CPU, let's see some differences between multi-process and multi-thread:
- It is difficult to share data between different processes, but threads can share memory.
- Processes consume more computer resources than threads.
- The processes will not affect each other, a thread hanging up will cause the whole process to hang up.
The ChildProcessPool
is degisned for those nodejs applications with multi-process architecture. E.g. in the demo file-slice-upload, I use ChildProcessPool
to manage thousands of uploading tasks and handle file reading and writing.
1. Create a childprocess pool
- path - the absolute path to a js file
- max - the max count of instance created by pool
- env - env variable
const { ChildProcessPool } = require('electron-re');
global.ipcUploadProcess = new ChildProcessPool({
path: path.join(app.getAppPath(), 'app/services/child/upload.js'),
max: 6,
env: { lang: global.lang, NODE_ENV: nodeEnv }
});
2. Send request to a process instance
- 1)params -
taskName
A task registried with ProcessHost
, it's neccessary. - 2)params -
data
The data passed to process, it's neccessary. - 3)params -
id
The unique id bound to a process instance. Sometime you send request to a process with special data, then expect to get callback data from that, you can give a unique id in send
function. Each time ChildProcessPool
will send a request to the process bound with this id, OR if give a empty/undefined/null id, pool will select a process random.
global.ipcUploadProcess.send(
'init-works',
{
name: 'fileName',
type: 'fileType',
size: 'fileSize',
},
uploadId
)
.then((rsp) => {
console.log(rsp);
});
3. Send request to all process instances
- 1)params -
taskName
A task registried with ProcessHost
(check usage below), it's neccessary. - 2)params -
data
The data passed to process, it's neccessary.
global.ipcUploadProcess.sendToAll(
'record-get-all',
{ data: 'test' }
)
.then((rsp) => {
console.log(rsp);
});
VI ) Instruction 4: ProcessHost
working with ChildProcessPool
In Instruction 3
, we already know how to create a sub-process pool and send request using it. Now let's figure out how to registry a task and handle process messages in a sub process(created by ChildProcessPool constructor with param - path
).
Using ProcessHost
we will no longer pay attention to the message sending/receiving between main process and sub processes. Just declaring a task with a unique service-name and put your processing code into a function. And remember that if the code is async, return a Promise instance instead.
1. Require it in a sub process
const { ProcessHost } = require('electron-re');
2. Registry a task with unique name
ProcessHost
.registry('init-works', (params) => {
return initWorks(params);
})
.registry('async-works', (params) => {
return asyncWorks(params);
});
function initWorks(params) {
console.log(params);
return params;
}
function AsyncWorks(params) {
console.log(params);
return fetch(url);
}
3. Working with ChildProcessPool
global.ipcUploadProcess.send(
'init-works',
{
name: 'fileName',
type: 'fileType',
size: 'fileSize',
},
uploadId
);
...
...
VII ) Examples
-
electronux - A project of mine that uses BroserService
and MessageChannel
of electron-re.
-
file-slice-upload - A demo about parallel upload of multiple files, it uses ChildProcessPool
and ProcessHost
of electron-re, based on Electron@9.3.5.
-
Also you can check the index.dev.js
and test
dir in root, there are some cases for a full usage.