'Is there any way that I can run child_process in electronjs and send json file to react that is being created by that child_process?

  1. I have a CLI application that is built using nodejs, I can pass some flags and it will output to a JSON file. Now I want to make it a desktop application that will show that JSON file data to UI. I'm trying to use reactjs for UI. Also, have to store some information that is going to be used to run the CLI.

  2. I have tried to integrate the CLI app to electronjs using child_process (exec(), spawn()) but it's not working and I couldn't figure out why. Also, I have tried to use expressjs server wrapper that is going to spawn the CLI app child_process and send the output with JSON response but it seems like failed to create the JSON file.

  3. const timestamp = Date.now();
    
    const child = childProcess.exec(`node ./index.js --config ./config.js ${flags} --json ${timestamp}.json --console none`);
    
    child.stdout.on('data', (d) => console.log(d.toString()));
    
    child.on('close', () =>{
     res.status(200).json({file: `${timestamp}.json`}); // I want to send this data to frontend
    });
    

I'm also interested to use some other library/framework of javascript to achieve this.



Solution 1:[1]

To communicate safely between threads, Electron some time ago introduced the use of a prelaod.js script. This script is effectively the gatekeeper between the main thread and render thread(s). Electron's Context Isolation page will explain this in more detail.

In addition to this, communication between threads is performed by Inter-Process Communication. These IPC commands are used to initiate and listen for communication over 'channels'. Channels are just names of your choosing along with which data can be attached (if needed).


The main component one needs to understand and implement is the preload.js script.

Here, you include whitelisted channel names. Any name format can be used. Any channel name not included in these lists will be denied communication between threads.

PS: Many people (including some Electron documentation) include actual function implementation directly within the preload script. Whilst there is nothing wrong with this it definitely does make things more complicated than necessary. Using the preload script to manage channel names only, frees up implementation of the channel names elsewhere which greatly simplifies things. IE: Separation of Concerns.

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': [],
        // From main to render.
        'receive': [
            'config:data'
        ],
        // 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);
            }
        }
    }
);

Note, within the whitelisted receive array I have included the channel name config:data.


Now, within your main thread, send the contents of the json file via the previously designated channel.

main.js (main thread)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;

const nodePath = require("path");

let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });
        .then(() => { runConfig(); }); // Now, run your config.js file

    return window;
}

function runConfig() {
    const timestamp = Date.now();
    const child = childProcess.exec(`node ./index.js --config ./config.js ${flags} --json ${timestamp}.json --console none`);

    child.stdout.on('data', (d) => console.log(d.toString()));
    
    child.on('close', () => {
        let jsonData = JSON.parse(nodeFs.readFileSync(`${timestamp}.json`, 'utf8'));
        console.log(jsonData);
        window.webContents.send('config:data', jsonData); // Send the data to the render thread
    });
}

electronApp.on('ready', () => {
    window = createWindow();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

Lastly, listen for the channel name to be called in the render thread.

index.html (render thread)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>

    <body></body>
    
    <script>
        window.ipcRender.receive('config:data', (jsonData) => {
            console.log(jsonData);
        });
    </script>
</html>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 midnight-coding