Electron’s strength lies in its flexible integration capabilities, enabling developers to combine front-end frameworks (such as React, Vue, Angular) and native APIs (like child_process) to build feature-rich desktop applications. This tutorial explores how to develop complex applications with Electron, integrate mainstream front-end frameworks, and extend functionality through Node.js native modules. Through step-by-step code analysis and a comprehensive case study, you will master advanced Electron integration techniques.
Electron API Integration Basics
Overview of Electron Integration Capabilities
- Front-End Frameworks: Supports React, Vue, Angular, etc., enhancing UI development efficiency.
- Native APIs: Access file systems, processes, and networks via Node.js.
- Cross-Platform: One codebase runs on Windows, macOS, and Linux.
Requirements for Building Complex Desktop Applications
- Multi-Window Management: Support for multiple independent interfaces.
- State Management: Handle complex data flows.
- Native Functionality: Execute system commands or interact with hardware interfaces.
Development Environment Setup
Initialize a Project
mkdir ElectronIntegratedDemo
cd ElectronIntegratedDemo
npm init -y
npm install electron --save-devConfigure package.json
{
"name": "electron-integrated-demo",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^28.2.2"
}
}Building Complex Desktop Applications with Electron
Planning a Complex Application Architecture
- Main Process: Manages windows, native functionality, and communication.
- Renderer Process: Runs front-end frameworks for UI handling.
- Modular Structure: Separates utility functions and state management.
Example Architecture
ElectronIntegratedDemo/
├── main.js # Main process
├── preload.js # Preload script
├── src/
│ ├── renderer.js # Renderer process entry
│ ├── components/ # Front-end components
│ ├── utils/ # Utility functions
├── index.html # Main window pageConfiguring Multi-Window and Modular Structure
Example Code
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
let mainWin, toolWin;
function createMainWindow() {
mainWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
mainWin.loadFile('index.html');
mainWin.on('closed', () => {
mainWin = null;
if (toolWin) toolWin.close();
});
}
function createToolWindow() {
if (toolWin) {
toolWin.focus();
return;
}
toolWin = new BrowserWindow({
width: 400,
height: 300,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
toolWin.loadFile('tool.html');
toolWin.on('closed', () => {
toolWin = null;
});
}
app.whenReady().then(() => {
createMainWindow();
});
ipcMain.on('open-tool', () => createToolWindow());
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});Analysis
- Multi-Window: Main and tool windows.
- Modular Structure: Separates window creation logic.
Integrating Electron with Other Libraries
Integrating React
Installation
npm install react react-dom redux react-redux redux-thunk --save
npm install webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react --save-devConfigure webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/renderer.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'renderer.bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
};src/store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const initialState = { tasks: [] };
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TASK':
return { ...state, tasks: [...state.tasks, action.payload] };
default:
return state;
}
};
export default createStore(reducer, applyMiddleware(thunk));src/App.js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
const App = () => {
const [task, setTask] = useState('');
const tasks = useSelector(state => state.tasks);
const dispatch = useDispatch();
const addTask = () => {
if (task.trim()) {
dispatch({ type: 'ADD_TASK', payload: { id: Date.now(), text: task } });
setTask('');
}
};
return (
<div>
<h1>Task Manager</h1>
<input value={task} onChange={e => setTask(e.target.value)} placeholder="Enter task" />
<button onClick={addTask}>Add</button>
<button onClick={() => window.electronAPI.openTool()}>Open Tool</button>
<ul>
{tasks.map(task => (
<li key={task.id}>{task.text}</li>
))}
</ul>
</div>
);
};
export default App;src/renderer.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);index.html
<!DOCTYPE html>
<html>
<head>
<title>React Task Manager</title>
</head>
<body>
<div id="root"></div>
<script src="./dist/renderer.bundle.js"></script>
</body>
</html>main.js
ipcMain.on('open-tool', () => createToolWindow());preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openTool: () => ipcRenderer.send('open-tool'),
});Build and Run
npx webpack
npm startAnalysis
- React: Component-based development.
- Redux: State management.
Integrating Vue
Installation
npm install vue vuex vue-loader vue-template-compiler --saveConfigure webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
module: {
rules: [
{ test: /\.vue$/, loader: 'vue-loader' },
{
test: /\.js$/,
exclude: /node_modules/,
use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } },
},
],
},
plugins: [new VueLoaderPlugin()],
};src/App.vue
<template>
<div>
<h1>Task Manager</h1>
<input v-model="newTask" @keypress.enter="addTask" placeholder="Enter task">
<button @click="addTask">Add</button>
<button @click="openTool">Open Tool</button>
<ul>
<li v-for="task in tasks" :key="task.id">{{ task.text }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return { newTask: '' };
},
computed: {
tasks() {
return this.$store.state.tasks;
},
},
methods: {
addTask() {
if (this.newTask.trim()) {
this.$store.dispatch('addTask', { id: Date.now(), text: this.newTask });
this.newTask = '';
}
},
openTool() {
window.electronAPI.openTool();
},
},
};
</script>src/store.js
import Vuex from 'vuex';
export default new Vuex.Store({
state: { tasks: [] },
mutations: {
addTask(state, task) {
state.tasks.push(task);
},
},
actions: {
addTask({ commit }, task) {
commit('addTask', task);
},
},
});src/renderer.js
import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';
import store from './store';
Vue.use(Vuex);
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App),
}).$mount('#root');index.html
<div id="root"></div>
<script src="./dist/renderer.bundle.js"></script>Analysis
- Vue: Component-based UI.
- Vuex: State management.
Integrating Angular
Installation
npm install -g @angular/cli
ng new angular-app --skip-git --skip-tests
cd angular-app
npm install electron --save-devConfigure main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
win.loadURL('http://localhost:4200');
}
app.whenReady().then(createWindow);angular.json
{
"projects": {
"angular-app": {
"architect": {
"build": {
"options": {
"outputPath": "dist/angular-app"
}
}
}
}
}
}src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>Task Manager</h1>
<input [(ngModel)]="newTask" (keypress)="onKeyPress($event)">
<button (click)="addTask()">Add</button>
<button (click)="openTool()">Open Tool</button>
<ul>
<li *ngFor="let task of tasks">{{ task.text }}</li>
</ul>
`,
})
export class AppComponent {
newTask = '';
tasks = [];
addTask() {
if (this.newTask.trim()) {
this.tasks.push({ id: Date.now(), text: this.newTask });
this.newTask = '';
}
}
onKeyPress(event: KeyboardEvent) {
if (event.key === 'Enter') this.addTask();
}
openTool() {
(window as any).electronAPI.openTool();
}
}Run
ng serve & npm startAnalysis
- Angular: Structured application framework.
- Electron: Loads Angular development server.
Deep Integration with Native APIs
Using child_process to Call System Commands
Example Code
main.js
const { exec } = require('child_process');
ipcMain.handle('run-command', async (event, command) => {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) reject(error.message);
else resolve(stdout || stderr);
});
});
});preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
runCommand: (command) => ipcRenderer.invoke('run-command', command),
});Handling Child Process Output and Errors
Example Code
index.html
<button id="run">Run Command</button>
<div id="output"></div>
<script>
const runButton = document.getElementById('run');
const output = document.getElementById('output');
runButton.addEventListener('click', async () => {
try {
const result = await window.electronAPI.runCommand('dir'); // Windows example
output.textContent = result;
} catch (error) {
output.textContent = 'Error: ' + error;
}
});
</script>Analysis
exec: Executes system commands.- Error Handling: Captures stderr output.
Integrating Other Node.js Native Modules
Example: Using os Module
const os = require('os');
ipcMain.handle('get-system-info', async () => {
return {
platform: os.platform(),
memory: os.totalmem(),
};
});Analysis
os: Retrieves system information.
Comprehensive Case Study: Multi-Function System Tool
Requirement Analysis
Build a system tool that:
- Displays a task list in the main window (React).
- Executes system commands in a tool window.
- Integrates
child_processto retrieve system information.
Step-by-Step Code Analysis
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const { exec } = require('child_process');
const path = require('path');
let mainWin, toolWin;
function createMainWindow() {
mainWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
mainWin.loadFile('index.html');
}
function createToolWindow() {
toolWin = new BrowserWindow({
width: 400,
height: 300,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
toolWin.loadFile('tool.html');
}
app.whenReady().then(() => {
createMainWindow();
});
ipcMain.on('open-tool', () => createToolWindow());
ipcMain.handle('run-command', async (event, command) => {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) reject(error.message);
else resolve(stdout || stderr);
});
});
});
ipcMain.handle('get-system-info', async () => {
return {
platform: require('os').platform(),
memory: require('os').totalmem(),
};
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openTool: () => ipcRenderer.send('open-tool'),
runCommand: (command) => ipcRenderer.invoke('run-command', command),
getSystemInfo: () => ipcRenderer.invoke('get-system-info'),
});src/store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const initialState = { tasks: [], systemInfo: null };
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TASK':
return { ...state, tasks: [...state.tasks, action.payload] };
case 'SET_SYSTEM_INFO':
return { ...state, systemInfo: action.payload };
default:
return state;
}
};
export default createStore(reducer, applyMiddleware(thunk));
export const loadSystemInfo = () => async (dispatch) => {
const info = await window.electronAPI.getSystemInfo();
dispatch({ type: 'SET_SYSTEM_INFO', payload: info });
};src/App.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadSystemInfo } from './store';
const App = () => {
const [task, setTask] = useState('');
const tasks = useSelector(state => state.tasks);
const systemInfo = useSelector(state => state.systemInfo);
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadSystemInfo());
}, [dispatch]);
const addTask = () => {
if (task.trim()) {
dispatch({ type: 'ADD_TASK', payload: { id: Date.now(), text: task } });
setTask('');
}
};
return (
<div>
<h1>System Tool</h1>
<input value={task} onChange={e => setTask(e.target.value)} placeholder="Enter task" />
<button onClick={addTask}>Add</button>
<button onClick={() => window.electronAPI.openTool()}>Open Tool</button>
<ul>
{tasks.map(task => (
<li key={task.id}>{task.text}</li>
))}
</ul>
{systemInfo && (
<div>
<p>Platform: {systemInfo.platform}</p>
<p>Memory: {systemInfo.memory / 1024 / 1024 / 1024} GB</p>
</div>
)}
</div>
);
};
export default App;src/renderer.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);index.html
<!DOCTYPE html>
<html>
<head>
<title>System Tool</title>
</head>
<body>
<div id="root"></div>
<script src="./dist/renderer.bundle.js"></script>
</body>
</html>tool.html
<!DOCTYPE html>
<html>
<head>
<title>Tool Window</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
input { width: 100%; padding: 8px; margin-bottom: 10px; }
button { padding: 8px 16px; background-color: #007bff; color: white; }
pre { white-space: pre-wrap; }
</style>
</head>
<body>
<h2>Run Command</h2>
<input id="commandInput" placeholder="Enter command">
<button id="run">Run</button>
<pre id="output"></pre>
<script>
const input = document.getElementById('commandInput');
const runButton = document.getElementById('run');
const output = document.getElementById('output');
runButton.addEventListener('click', async () => {
const command = input.value.trim();
if (command) {
try {
const result = await window.electronAPI.runCommand(command);
output.textContent = result;
} catch (error) {
output.textContent = 'Error: ' + error;
}
}
});
</script>
</body>
</html>package.json
{
"name": "system-tool",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "webpack && electron-builder"
},
"build": {
"appId": "com.example.systemtool",
"files": ["main.js", "index.html", "tool.html", "dist/**/*", "preload.js"]
},
"devDependencies": {
"electron": "^28.2.2",
"electron-builder": "^24.9.1",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"babel-loader": "^9.1.3",
"@babel/core": "^7.24.5",
"@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.24.1"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"redux": "^5.0.1",
"react-redux": "^9.1.2",
"redux-thunk": "^3.1.0"
}
}Run and Build
npx webpack
npm start
npm run buildAnalysis
- React: Handles UI and state management.
child_process: Executes system commands.- Multi-Window: Main and tool windows.
Advanced Techniques and Considerations
Performance Optimization Tips
- Lazy Loading Frameworks:
const React = await import('react');- Limit Child Processes: Avoid excessive concurrent processes.
Debugging Integration Issues
- Developer Tools:
win.webContents.openDevTools() - Logging:
exec('cmd', (err, stdout) => console.log(err || stdout));



