Electron: main и renderer процессы

Приложение на базе Electron состоит из двух фундаментально разных типов процессов:

  • main process — основной процесс приложения;
  • renderer process — процессы интерфейса.

Такое разделение повторяет архитектуру браузера Chromium, внутри которого один процесс управляет жизненным циклом приложения, а отдельные процессы отвечают за отображение пользовательского интерфейса.

Основной процесс запускается первым и управляет:

  • окнами приложения;
  • системными событиями;
  • доступом к файловой системе;
  • интеграцией с операционной системой;
  • созданием renderer-процессов;
  • безопасностью приложения;
  • межпроцессным взаимодействием.

Renderer-процесс представляет собой обычную веб-страницу, работающую внутри Chromium. Именно renderer отвечает за:

  • отображение HTML;
  • работу CSS;
  • выполнение клиентского JavaScript;
  • реакцию на пользовательские события;
  • визуальную часть приложения.

Одно Electron-приложение может содержать:

  • один main process;
  • множество renderer processes.

Main process

Точка входа приложения

Main process начинается с файла, указанного в поле main внутри package.json.

Пример:

{
  "name": "electron-app",
  "main": "main.js"
}

Файл main.js становится ядром приложения.


Создание окна через BrowserWindow

Главная задача main process — создание окон.

Пример минимального main process:

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

function createWindow() {
    const win = new BrowserWindow({
        width: 1200,
        height: 800
    });

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

app.whenReady().then(createWindow);

Что происходит в этом коде

  1. Electron запускает основной процесс.
  2. Модуль app ожидает готовности движка Chromium.
  3. Создаётся экземпляр BrowserWindow.
  4. Chromium создаёт renderer process.
  5. В renderer загружается index.html.

Жизненный цикл приложения

Событие ready

Событие ready означает, что Electron и Chromium полностью инициализированы.

app.whenReady().then(() => {
    console.log('Application ready');
});

До наступления готовности нельзя создавать окна.


Событие window-all-closed

Вызывается после закрытия всех окон.

app.on('window-all-closed', () => {
    app.quit();
});

На macOS поведение обычно отличается: приложение остаётся активным даже без открытых окон.

Пример:

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

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

Событие activate

Используется преимущественно на macOS.

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

Объект BrowserWindow

BrowserWindow — оболочка над нативным окном операционной системы.


Настройка размеров окна

const win = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 800,
    minHeight: 600
});

Безрамочные окна

const win = new BrowserWindow({
    frame: false
});

Такие окна используются для кастомных интерфейсов.


Прозрачность

const win = new BrowserWindow({
    transparent: true
});

Полноэкранный режим

const win = new BrowserWindow({
    fullscreen: true
});

Скрытие окна при старте

const win = new BrowserWindow({
    show: false
});

win.once('ready-to-show', () => {
    win.show();
});

Подход предотвращает мерцание интерфейса.


Renderer process

Renderer process — это изолированный браузерный процесс Chromium.

Он исполняет:

  • HTML;
  • CSS;
  • JavaScript;
  • DOM API;
  • Web API.

Каждое окно обычно имеет собственный renderer process.


Загрузка интерфейса

Локальный HTML

win.loadFile('index.html');

Удалённый URL

win.loadURL('https://example.com');

Код renderer process

Файл renderer.js:

document.getElementById('button').addEventListener('click', () => {
    console.log('clicked');
});

HTML:

<button id="button">Click</button>

<script src="renderer.js"></script>

Renderer напоминает обычное frontend-приложение.


Изоляция процессов

Главная особенность Electron — строгая изоляция между main и renderer.

Renderer по умолчанию:

  • не имеет полного доступа к Node.js;
  • не может напрямую управлять окнами;
  • не должен иметь доступа к системным API.

Main process выступает посредником между интерфейсом и операционной системой.


Node Integration

Включение Node.js внутри renderer

const win = new BrowserWindow({
    webPreferences: {
        nodeIntegration: true
    }
});

После этого renderer получает доступ к:

const fs = require('fs');

Почему nodeIntegration опасен

Если renderer загружает внешний контент, злоумышленник сможет получить доступ к:

  • файловой системе;
  • процессам ОС;
  • shell-командам;
  • сетевым API.

Поэтому современный Electron рекомендует:

nodeIntegration: false

Context Isolation

Современная безопасная конфигурация:

const win = new BrowserWindow({
    webPreferences: {
        contextIsolation: true,
        nodeIntegration: false
    }
});

Preload scripts

Preload script выполняется:

  • до загрузки renderer;
  • имеет доступ к Node.js;
  • работает как безопасный мост.

Пример:

const win = new BrowserWindow({
    webPreferences: {
        preload: path.join(__dirname, 'preload.js')
    }
});

Preload API

preload.js

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

contextBridge.exposeInMainWorld('api', {
    version: '1.0.0'
});

renderer.js

console.log(window.api.version);

Renderer получает только ограниченный API.


IPC — межпроцессное взаимодействие

Так как процессы изолированы, Electron использует IPC:

  • ipcMain
  • ipcRenderer

Отправка сообщения из renderer в main

renderer.js

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

ipcRenderer.send('save-file', {
    text: 'Hello'
});

main.js

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

ipcMain.on('save-file', (event, data) => {
    console.log(data);
});

Получение ответа от main

renderer.js

ipcRenderer.on('save-result', (event, result) => {
    console.log(result);
});

main.js

event.reply('save-result', {
    success: true
});

invoke и handle

Современный подход для request-response взаимодействия.

renderer.js

const result = await ipcRenderer.invoke('read-config');

main.js

ipcMain.handle('read-config', async () => {
    return {
        theme: 'dark'
    };
});

Почему invoke предпочтительнее

invoke/handle:

  • проще;
  • поддерживает async/await;
  • уменьшает количество каналов;
  • удобнее масштабируется.

Работа renderer через preload

Современный безопасный вариант:

preload.js

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

contextBridge.exposeInMainWorld('electronAPI', {
    loadConfig: () => ipcRenderer.invoke('load-config')
});

renderer.js

const config = await window.electronAPI.loadConfig();

Renderer не получает прямого доступа к Electron API.


Работа с файловой системой

Доступ к Node.js рекомендуется выполнять только в main process.

main.js

const fs = require('fs');

ipcMain.handle('read-file', async () => {
    return fs.readFileSync('data.txt', 'utf8');
});

Управление окнами

Main process полностью управляет окнами.


Открытие нового окна

const child = new BrowserWindow({
    parent: mainWindow,
    modal: true
});

Закрытие окна

win.close();

Скрытие окна

win.hide();

Показ окна

win.show();

Отправка сообщений конкретному окну

win.webContents.send('message', {
    text: 'Hello'
});

webContents

webContents — объект управления renderer process.

Он позволяет:

  • выполнять JavaScript;
  • отправлять события;
  • отслеживать загрузку страницы;
  • управлять DevTools.

Выполнение кода внутри renderer

win.webContents.executeJavaScript(`
    document.body.style.background = 'black';
`);

DevTools

win.webContents.openDevTools();

Навигационные события

win.webContents.on('did-finish-load', () => {
    console.log('loaded');
});

Sandbox

Electron поддерживает sandbox-режим.

const win = new BrowserWindow({
    webPreferences: {
        sandbox: true
    }
});

Sandbox дополнительно ограничивает renderer.


Архитектура безопасности

Безопасное Electron-приложение обычно использует:

webPreferences: {
    contextIsolation: true,
    sandbox: true,
    nodeIntegration: false,
    preload: preloadPath
}

Типичная архитектура Electron

Main layer

Содержит:

  • создание окон;
  • файловую систему;
  • меню;
  • tray;
  • IPC;
  • системную интеграцию.

Preload layer

Содержит:

  • безопасный bridge API;
  • валидацию данных;
  • IPC-обёртки.

Renderer layer

Содержит:

  • UI;
  • frontend-framework;
  • состояние приложения;
  • роутинг;
  • отображение данных.

Использование React/Vue/Angular

Frontend-фреймворки работают исключительно внутри renderer process.

Пример:

main
 ├── BrowserWindow
      └── React app

Electron не зависит от конкретного frontend-стека.


Несколько renderer-процессов

Каждое окно создаёт отдельный renderer process.

createMainWindow();
createSettingsWindow();
createAboutWindow();

Преимущества:

  • изоляция;
  • стабильность;
  • независимость интерфейсов.

Недостаток — повышенное потребление памяти.


Crash renderer process

Если renderer падает, main process продолжает работать.

win.webContents.on('render-process-gone', (event, details) => {
    console.log(details.reason);
});

Main process может:

  • перезапустить окно;
  • показать ошибку;
  • восстановить состояние.

Crash main process

Падение main process завершает всё приложение.

Поэтому:

  • main должен быть минимальным;
  • тяжёлая логика переносится в отдельные сервисы;
  • вычисления лучше выполнять в worker-процессах.

Worker Threads и renderer

Renderer может использовать:

new Worker('worker.js');

Это позволяет:

  • выносить тяжёлые вычисления;
  • не блокировать UI;
  • обрабатывать большие данные.

Renderer как браузер

Renderer поддерживает:

  • Canvas;
  • WebGL;
  • WebRTC;
  • WebSocket;
  • IndexedDB;
  • Service Workers.

Electron фактически запускает полноценный Chromium.


Remote module

Ранее Electron предоставлял модуль:

require('@electron/remote');

Он позволял renderer напрямую вызывать main API.

Подход оказался небезопасным:

  • нарушал изоляцию;
  • усложнял безопасность;
  • увеличивал поверхность атак.

Современные приложения используют IPC и preload API вместо remote.


Схема взаимодействия процессов

┌────────────────────┐
│     Main Process   │
│────────────────────│
│ BrowserWindow      │
│ app lifecycle      │
│ filesystem         │
│ native API         │
│ IPC handlers       │
└─────────┬──────────┘
          │ IPC
          ▼
┌────────────────────┐
│   Preload Script   │
│────────────────────│
│ contextBridge      │
│ secure API         │
└─────────┬──────────┘
          │
          ▼
┌────────────────────┐
│ Renderer Process   │
│────────────────────│
│ HTML               │
│ CSS                │
│ React/Vue          │
│ DOM                │
│ UI                 │
└────────────────────┘