Lesson 01-Electron Basics Introduction

Introduction to Electron Basics

Installing Node.js and npm

Electron relies on Node.js and npm (Node Package Manager).

Installation Steps

Download Node.js:

  • Visit the Node.js official website.
  • Recommended LTS version (e.g., 16.x or 18.x; as of February 2025, use the latest LTS).

Installation:

  • Windows/Mac: Run the installer.
  • Linux:
sudo apt update
sudo apt install nodejs npm

Verify Installation:

node -v  # Example output: v18.18.0
npm -v   # Example output: 9.8.1

Notes

  • Ensure npm is compatible with the Node.js version.

Installing Electron

Global Installation (Optional)

npm install -g electron
electron --version  # Verify installation

Create Project Directory:

mkdir MyFirstElectronApp
cd MyFirstElectronApp
npm init -y

Install Electron:

npm install electron --save-dev

Modify package.json

{
  "name": "my-first-electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^28.2.2"
  }
}

Creating Your First Electron Application

Create Main Process File (main.js)

const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });

  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();

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

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

Create Renderer Process File (index.html)

<!DOCTYPE html>
<html>
<head>
  <title>Hello, Electron!</title>
</head>
<body>
  <h1>Hello, Electron!</h1>
</body>
</html>

Run the Application

npm start

Analysis

  • main.js: Defines the main process and creates the window.
  • index.html: Defines the UI for the renderer process.
  • npm start: Launches Electron.

Using Electron’s Main and Renderer Processes

Main Process Calls

// main.js
const { ipcMain } = require('electron');

ipcMain.on('message-from-renderer', (event, arg) => {
  console.log('Renderer says:', arg);
  event.reply('message-to-renderer', 'Hello from main!');
});

Renderer Process Calls

<!DOCTYPE html>
<html>
<head>
  <title>Hello, Electron!</title>
</head>
<body>
  <h1>Hello, Electron!</h1>
  <button onclick="sendMessage()">Send Message</button>
  <script>
    const { ipcRenderer } = require('electron');
    function sendMessage() {
      ipcRenderer.send('message-from-renderer', 'Hello from renderer!');
      ipcRenderer.on('message-to-renderer', (event, arg) => {
        console.log(arg); // "Hello from main!"
      });
    }
  </script>
</body>
</html>

Analysis

  • ipcMain: Main process receives messages.
  • ipcRenderer: Renderer process sends and receives messages.

Electron Fundamentals

Understanding Electron Architecture: Main and Renderer Processes

  • Main Process:
    • Runs Node.js, controls application lifecycle.
    • Manages windows and system interactions.
  • Renderer Process:
    • Runs Chromium, renders UI.
    • Each window has its own renderer process.

Architecture Diagram

[Main Process]
  ├── Creates and manages windows
  └── Interacts with system
[Renderer Process 1] [Renderer Process 2]
  └── UI Rendering    └── UI Rendering

Main Process: Controlling Application Lifecycle

Extended Example (main.js)

const { app, BrowserWindow, Menu } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  win.loadFile('index.html');

  // Custom Menu
  const menu = Menu.buildFromTemplate([
    { label: 'File', submenu: [{ label: 'Quit', click: () => app.quit() }] },
  ]);
  Menu.setApplicationMenu(menu);
}

app.whenReady().then(createWindow);

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

Analysis

  • app.whenReady: Creates window when the app is ready.
  • Menu: Controls application menu.

Renderer Process: Handling User Interface and Web Rendering

Extended Example (index.html)

<!DOCTYPE html>
<html>
<head>
  <title>Hello, Electron!</title>
</head>
<body>
  <h1>Hello, Electron!</h1>
  <button onclick="sendMessage()">Send Message</button>
  <script>
    const { ipcRenderer } = require('electron');
    function sendMessage() {
      ipcRenderer.send('message-from-renderer', 'Hello from renderer!');
      ipcRenderer.on('message-to-renderer', (event, arg) => {
        console.log(arg);
      });
    }
  </script>
</body>
</html>

Analysis

  • HTML/CSS: Defines UI and styles.
  • JavaScript: Handles interaction logic.

HTML, CSS, JavaScript in Electron

  • HTML: Structures the interface.
  • CSS: Styles components.
  • JavaScript:
    • Main Process: Uses Node.js APIs.
    • Renderer Process: Combines DOM and Electron APIs.

Example: Dynamic Styling

<!DOCTYPE html>
<html>
<head>
  <style>
    .dynamic { color: blue; }
  </style>
</head>
<body>
  <h1 class="dynamic">Hello, Electron!</h1>
</body>
</html>

Application Structure

Project Directory Structure

MyFirstElectronApp/
├── main.js          # Main process file
├── index.html       # Renderer process entry
├── package.json     # Project configuration
├── node_modules/    # Dependencies
├── assets/          # Static resources (e.g., images)

Main Process File (main.js)

  • Functionality: Window management, event listening.
  • Extension:
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'),
    },
  });
  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

Renderer Process Files (HTML, CSS, JavaScript)

preload.js

const { contextBridge } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  sendMessage: (msg) => require('electron').ipcRenderer.send('message-from-renderer', msg),
});

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Hello, Electron!</title>
</head>
<body>
  <h1>Hello, Electron!</h1>
  <button onclick="window.electronAPI.sendMessage('Hello')">Send Message</button>
</body>
</html>

Analysis

  • preload.js: Safely exposes APIs.
  • Renderer Process: Uses preload script to communicate with the main process.

Electron Core Architecture

Division of Responsibilities: Main and Renderer Processes

Main Process

Core Responsibilities
// Main Process Typical Responsibilities Example
const { app, BrowserWindow, ipcMain } = require('electron');

// 1. Application Lifecycle Management
app.whenReady().then(() => {
  // Create Window
});

// 2. System-Level Functionality Control
const fs = require('fs');
fs.writeFileSync('/path/to/file', 'data'); // Direct File System Access

// 3. Window Management
const win = new BrowserWindow({ /* Configuration */ });
win.loadURL('https://example.com');

// 4. Native Menus and Dialogs
const { Menu, dialog } = require('electron');
Menu.setApplicationMenu(/* Custom Menu */);
dialog.showOpenDialog(/* File Selection Dialog */);
Privileged Capabilities
  • System-level API access (file system, network, native dialogs)
  • Window lifecycle control
  • Native menu and clipboard management
  • System tray and global shortcuts

Renderer Process

Core Responsibilities
// Renderer Process Typical Responsibilities Example
// 1. UI Rendering and Interaction
document.getElementById('btn').addEventListener('click', () => {
  // Handle User Interaction
});

// 2. Web Technology Stack Development
import React from 'react'; // Use Modern Web Frameworks
import axios from 'axios'; // Make Network Requests

// 3. Communicate with Main Process via IPC
const { ipcRenderer } = require('electron');
ipcRenderer.invoke('read-file', '/path/to/file');
Limitations and Constraints
  • Restricted by browser security sandbox
  • Cannot directly access system-level APIs
  • Requires IPC to communicate with the main process for privileged operations

Process Model Comparison

FeatureMain ProcessRenderer Process
Process Count1 (can create multiple windows)One per window
System API AccessFull accessRestricted access
Crash ImpactCrashes entire applicationAffects only the current window
Memory ManagementRequires careful managementCan be independently reclaimed
Typical UseSystem integration, window managementUI rendering, business logic

Inter-Process Communication (IPC) Mechanism

ipcMain and ipcRenderer

Main Process Event Listening
// main.js
const { ipcMain } = require('electron');

// Synchronous Communication
ipcMain.on('sync-message', (event, arg) => {
  event.returnValue = `Synchronous Reply: ${arg}`;
});

// Asynchronous Communication
ipcMain.handle('async-message', async (event, arg) => {
  const result = await someAsyncOperation(arg);
  return `Asynchronous Reply: ${result}`;
});
Renderer Process Event Sending
// renderer.js
const { ipcRenderer } = require('electron');

// Synchronous Call
const reply = ipcRenderer.sendSync('sync-message', 'ping');
console.log(reply); // "Synchronous Reply: ping"

// Asynchronous Call
ipcRenderer.invoke('async-message', 'pong').then((result) => {
  console.log(result); // "Asynchronous Reply: pong"
});

Communication Performance Optimization Strategies

  1. Batch Data Transfer: Reduce cross-process call frequency.
  2. Shared Memory: Expose limited APIs via contextBridge.
  3. Communication Protocol Optimization: Use binary format instead of JSON.
  4. Channel Reuse: Avoid frequent creation/destruction of communication channels.

Chromium and Node.js Integration

Key Integration Technologies

V8 Engine Sharing:

  • Chromium and Node.js use the same V8 instance.
  • Reduces memory usage and context-switching overhead.

Node.js Integration Mode:

// BrowserWindow Configuration Example
new BrowserWindow({
  webPreferences: {
    nodeIntegration: true,      // Enable Node.js Integration
    contextIsolation: false     // Disable Context Isolation (Not Recommended)
  }
});

Modern Security Practices:

// Recommended Security Configuration
new BrowserWindow({
  webPreferences: {
    nodeIntegration: false,     // Disable Automatic Integration
    contextIsolation: true,     // Enable Context Isolation
    preload: path.join(__dirname, 'preload.js') // Use Preload Script
  }
});

Context Isolation and Preload Scripts

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

// Safely Expose APIs
contextBridge.exposeInMainWorld('electronAPI', {
  readFile: (path) => ipcRenderer.invoke('read-file', path)
});

// Use in Renderer Process
window.electronAPI.readFile('/path/to/file');

Native Modules and System API Access

Development Steps Example

Create C++ Module:

// native-addon.cc
#include <napi.h>

napi_value Hello(napi_env env, napi_callback_info info) {
  napi_value result;
  napi_create_string_utf8(env, "Hello from C++", NAPI_AUTO_LENGTH, &result);
  return result;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) {
  napi_property_descriptor desc = {"hello", 0, Hello, 0, 0, 0, napi_default, 0};
  napi_define_properties(env, exports, 1, &desc);
}

Compilation Configuration:

# binding.gyp
{
  "targets": [{
    "target_name": "native-addon",
    "sources": ["native-addon.cc"]
  }]
}

Electron Integration:

# Recompile with electron-rebuild
npx electron-rebuild -f -w native-addon

System API Access Capabilities

System FunctionImplementation MethodExample Use Case
File System OperationsNode.js fs moduleRead/write configuration files
Native Dialogselectron.dialog moduleFile selection/saving
System Notificationselectron.notification moduleDesktop notifications
Hardware Device AccessNative modules (C++)USB device communication
Window Managementelectron.BrowserWindowMulti-window control

Application Packaging and Distribution

Packaging Tool Comparison

ToolFeaturesUse Case
electron-builderComprehensive, supports auto-updatesCommercial application distribution
electron-packagerLightweight, highly configurableCustom packaging needs

electron-builder Configuration Example

// package.json
{
  "build": {
    "appId": "com.example.app",
    "productName": "MyApp",
    "directories": {
      "output": "dist"
    },
    "files": [
      "dist/**/*",
      "package.json"
    ],
    "win": {
      "target": ["nsis"],
      "icon": "build/icon.ico"
    },
    "mac": {
      "target": ["dmg"],
      "icon": "build/icon.icns"
    },
    "linux": {
      "target": ["AppImage"],
      "icon": "build/icon.png"
    }
  }
}

Packaging Process Details

  1. Code Packaging:
    • Use Webpack/Rollup to bundle frontend resources.
    • Copy Node.js dependencies to the build directory.
  2. Resource Handling:
    • Convert icons (ICO/ICNS/PNG).
    • Compile binary dependencies.
  3. Installer Generation:
    • Windows: NSIS/Squirrel installers.
    • macOS: DMG/App Store packages.
    • Linux: AppImage/DEB/RPM.

Auto-Update Implementation

// Main Process Auto-Update Code
const { autoUpdater } = require('electron');

autoUpdater.setFeedURL({
  provider: 'github',
  repo: 'my-app',
  owner: 'my-org',
  private: false
});

autoUpdater.checkForUpdatesAndNotify();

// Renderer Process Listens for Update Events
window.electronAPI.onUpdateAvailable(() => {
  // Prompt User to Restart for Update
});

Distribution Channel Strategies

  1. Official Website Downloads:
    • Provide installers for each platform.
    • Integrate auto-update services.
  2. App Store Distribution:
    • macOS App Store.
    • Microsoft Store.
    • Linux distribution repositories.
  3. Enterprise Distribution:
    • Internal server deployment.
    • Private update server configuration.

Through the above architectural design and toolchain support, Electron enables an efficient development model that combines web technologies with native system capabilities, allowing developers to build cross-platform desktop applications using familiar web technology stacks.

Comprehensive Case Study: Simple Notepad Application

Requirements Analysis

Implement a simple notepad:

  • Main window displays a text input box.
  • Save and read files.
  • Use main and renderer processes for communication.

Implementation Code Breakdown

main.js

const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const fs = require('fs').promises;
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.loadFile('index.html');
}

app.whenReady().then(createWindow);

ipcMain.handle('save-file', async (event, content) => {
  const { filePath } = await dialog.showSaveDialog({
    defaultPath: 'note.txt',
    filters: [{ name: 'Text Files', extensions: ['txt'] }],
  });
  if (filePath) {
    await fs.writeFile(filePath, content);
    return filePath;
  }
  return null;
});

ipcMain.handle('load-file', async () => {
  const { filePaths } = await dialog.showOpenDialog({
    filters: [{ name: 'Text Files', extensions: ['txt'] }],
  });
  if (filePaths && filePaths.length > 0) {
    const content = await fs.readFile(filePaths[0], 'utf8');
    return content;
  }
  return null;
});

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

preload.js

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  saveFile: (content) => ipcRenderer.invoke('save-file', content),
  loadFile: () => ipcRenderer.invoke('load-file'),
});

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Simple Notepad</title>
</head>
<body>
  <h1>Simple Notepad</h1>
  <textarea id="content" rows="10" cols="50"></textarea>
  <br>
  <button onclick="saveFile()">Save</button>
  <button onclick="loadFile()">Load</button>
  <script>
    function saveFile() {
      const content = document.getElementById('content').value;
      window.electronAPI.saveFile(content).then(filePath => {
        if (filePath) alert('File saved: ' + filePath);
      });
    }
    function loadFile() {
      window.electronAPI.loadFile().then(content => {
        if (content) document.getElementById('content').value = content;
      });
    }
  </script>
</body>
</html>

package.json

{
  "name": "simple-notepad",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^28.2.2"
  }
}

Run

npm start

Analysis

  • Main Process: Handles file saving and reading.
  • Renderer Process: Displays UI and handles user interaction.
  • IPC: Facilitates secure communication via preload.js.

Advanced Techniques and Considerations

Performance Optimization Tips

  • Reduce IPC Calls: Batch data transfers.
  • Cache Resources: Store static files in assets/.
  • Asynchronous Operations: Use fs.promises for file operations.

Debugging and Error Handling

  • Developer Tools: Right-click window and select “Inspect”.
  • Logging:
console.log('Debug:', data);
  • Error Handling:
try {
  await fs.writeFile(filePath, content);
} catch (error) {
  console.error('Error:', error);
}
Share your love