Arquitectura de Electron

Se puede considerar Electron como:

Un contenedor de aplicaciones multiproceso, con un motor de navegador integrado (Chromium) y un runtime de Node.js,
Funciona de manera coordinada mediante comunicación entre procesos (IPC), logrando aplicaciones de escritorio de "página web + capacidades del sistema".

Sus componentes principales son:

Rol Descripción Analogía
Main Process (proceso principal) El cerebro de Electron; controla el ciclo de vida de la aplicación, crea ventanas y llama a las API del sistema Administrador del sistema operativo
Renderer Process (proceso de renderizado) Entorno web que se ejecuta en cada ventana (HTML, CSS, JS) Pestaña del navegador
Preload Script (script de precarga) Se ejecuta antes del renderizado; puede conectar las API del proceso principal para que las use la página web "Capa intermedia" de seguridad
IPC (comunicación entre procesos) Canal de comunicación entre el proceso principal y el proceso de renderizado Línea telefónica
Objeto BrowserWindow "Contenedor de ventana" creado por el proceso principal, donde se carga la página web Ventana del navegador
Módulo App Controla el ciclo de vida de la aplicación (inicio, salida) Control general

Proceso principal (Main Process)

El proceso principal es el "cerebro" de las aplicaciones Electron. Cada aplicación Electron tiene exactamente un proceso principal. Se encarga de:

  • Crear y gestionar las ventanas de la aplicación(proceso de renderizado)
  • Gestionar el ciclo de vida de la aplicación(inicio, salida, cambio entre primer plano y segundo plano)
  • Interactuar con las API nativas del sistema operativo
  • Gestionar componentes a nivel de sistema, como menús y diálogos

Ejemplo

// main.js - Ejemplo de proceso principal
const { app, BrowserWindow } = require('electron');

function createWindow() {
  // Crear la ventana del navegador
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  });

  // Cargar el index.html de la aplicación
  mainWindow.loadFile('index.html');
}

// Se llama cuando Electron completa la inicialización
app.whenReady().then(createWindow);

Proceso de renderizado (Renderer Process)

El proceso de renderizado se encarga de mostrar la interfaz de usuario. Cada ventana de Electron es un proceso de renderizado independiente:

  • Se ejecuta en el entorno del navegador Chromium
  • Construye la interfaz utilizando HTML, CSS y JavaScript
  • Cada ventana es un proceso independiente; no se afectan entre sí
  • Se comunica con el proceso principal mediante IPC

Ejemplo

<!-- index.html - Ejemplo de proceso de renderizado -->
<!DOCTYPE html>
<html>
<head>
    <title>Mi aplicación Electron</title>
</head>
<body>
    <h1>Hello Electron!</h1>
    <button id="btn">Haz clic en mí</button>
   
    <script>
// JavaScript en el proceso de renderizado
        document.getElementById('btn').addEventListener('click', () => {
alert('¡El botón ha sido clickeado!');
        });
    </script>
</body>
</html>

Scripts de precarga (Preload Scripts)

El script de precarga es el "puente" que conecta el proceso principal y el proceso de renderizado:

  • Se ejecuta antes de que el proceso de renderizado cargue la página web
  • Tiene acceso a las API de Node.js y al DOM
  • Expone de forma segura las API al proceso de renderizado mediante contextBridge

Ejemplo

// preload.js - Ejemplo de script de precarga
const { contextBridge, ipcRenderer } = require('electron');

// Expone API seguras al proceso de renderizado
contextBridge.exposeInMainWorld('electronAPI', {
  showDialog: (message) => ipcRenderer.invoke('show-dialog', message)
});

Comunicación entre procesos (IPC)

Modos de comunicación IPC

IPC(Inter-Process Communication)Es el núcleo de comunicación de Electron.

Dado que el proceso principal y el proceso de renderizado son independientes, los datos deben transferirse a través de IPC.

Dirección de comunicación

Tipo El proceso principal escucha El proceso de renderizado envía
Renderizado → Principal ipcMain.on(channel, handler) ipcRenderer.send(channel, data)
Principal → Renderizado event.sender.send(channel, data) ipcRenderer.on(channel, callback)

Ejemplo básico de comunicación

Código del proceso principal:

Ejemplo

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

// Escuchar mensajes del proceso de renderizado
ipcMain.handle('show-dialog', async (event, message) => {
  const result = await dialog.showMessageBox({
    type: 'info',
    message: message,
    buttons: ['Aceptar', 'Cancelar']
  });
  return result;
});

Código del proceso de renderizado:

Ejemplo

// Comunicarse a través de las API expuestas por el script de precarga
document.getElementById('btn').addEventListener('click', async () => {
  const result = await window.electronAPI.showDialog('¡Hola, Electron!');
  console.log('El usuario hizo clic en:', result.response);
});

Ventajas y características de la arquitectura

Comparación de ventajas

Característica Electron Desarrollo de escritorio tradicional
Tecnología de desarrollo Tecnología web (HTML/CSS/JS) Lenguajes nativos (C++/C#/Java)
Soporte multiplataforma Desarrollar una vez, ejecutar en múltiples plataformas Requiere desarrollo por separado para cada plataforma
Eficiencia de desarrollo Alta, aprovecha el ecosistema web existente Baja, requiere aprender tecnologías específicas de la plataforma
Rendimiento Relativamente bajo, consume más recursos Alto, rendimiento nativo
Tamaño del paquete de instalación Grande (incluye Chromium) Pequeño

Características principales

  1. Consistencia multiplataforma: la aplicación tiene la misma apariencia y comportamiento en todos los sistemas operativos
  2. Aprovechamiento del ecosistema web: se pueden usar directamente paquetes npm y frameworks web
  3. Desarrollo rápido de prototipos: basado en tecnologías web conocidas, con un ciclo de desarrollo corto
  4. API enriquecidas: proporciona un conjunto completo de API para acceder a las funciones nativas del sistema

Ejemplo de aplicación práctica

Creemos una aplicación simple de administrador de archivos para demostrar la arquitectura de Electron:

Proceso principal (main.js):

Ejemplo

const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const fs = require('fs').promises;
const path = require('path');

let mainWindow;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 700,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true
    }
  });

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

// Manejar la lectura de archivos
ipcMain.handle('read-file', async (event, filePath) => {
  try {
    const content = await fs.readFile(filePath, 'utf-8');
    return { success: true, content };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

// Manejar la selección de archivos
ipcMain.handle('select-file', async () => {
  const result = await dialog.showOpenDialog(mainWindow, {
    properties: ['openFile'],
    filters: [
      { name: 'Archivo de texto', extensions: ['txt', 'md', 'js', 'html', 'css'] }
    ]
  });
 
  return result;
});

app.whenReady().then(createWindow);

Script de precarga (preload.js):

Ejemplo

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

contextBridge.exposeInMainWorld('fileAPI', {
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  selectFile: () => ipcRenderer.invoke('select-file')
});

Proceso de renderizado (index.html):

Ejemplo

<!DOCTYPE html>
<html>
<head>
    <title>Administrador de archivos simple</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .container { max-width: 800px; margin: 0 auto; }
        button { padding: 10px 15px; margin: 5px; cursor: pointer; }
        #content { border: 1px solid #ccc; padding: 15px; margin-top: 10px;
                  min-height: 300px; white-space: pre-wrap; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Administrador de archivos simple</h1>
        <button id="selectBtn">Seleccionar archivo</button>
        <button id="clearBtn">Limpiar contenido</button>
       
        <div>
            <h3>Contenido del archivo:</h3>
            <div id="content">Seleccione un archivo, por favor...</div>
        </div>
    </div>

    <script>
        const selectBtn = document.getElementById('selectBtn');
        const clearBtn = document.getElementById('clearBtn');
        const contentDiv = document.getElementById('content');

        selectBtn.addEventListener('click', async () => {
            const result = await window.fileAPI.selectFile();
           
            if (!result.canceled && result.filePaths.length > 0) {
               const filePath = result.filePaths[0];
                const fileResult = await window.fileAPI.readFile(filePath);
               
                if (fileResult.success) {
contentDiv.textContent = `Ruta del archivo: ${filePath}\n\n${fileResult.content}`;
                } else {
contentDiv.textContent = `Error al leer el archivo: ${fileResult.error}`;
                }
            }
        });

        clearBtn.addEventListener('click', () => {
contentDiv.textContent = 'Seleccione un archivo, por favor...';
        });
    </script>
</body>
</html>

Otros módulos importantes (disponibles en el proceso principal)

Nombre del módulo Función
app Controla el ciclo de vida de la aplicación (inicio/salida)
Menu / MenuItem Crea la barra de menú de la aplicación
Tray Icono de bandeja del sistema
Notification Notificaciones del sistema
dialog Diálogo de abrir archivo/guardar
shell Abre archivos o URLs con el programa predeterminado del sistema
nativeImage Manipular imágenes (iconos)
clipboard Manipular el contenido del portapapeles
powerMonitor Escuchar eventos de energía del sistema
screen Obtener información de la pantalla (soporte para múltiples monitores)

Arquitectura de seguridad y aislamiento de sandbox

Como el proceso de renderizado puede ejecutar páginas web, para prevenir que el código malicioso ataque el sistema, Electron implementa el siguiente diseño de seguridad:

Política Descripción
Desactivar NodeIntegration Por defectofalse, para evitar que las páginas web llamen directamente a las API del sistema.
Habilitar ContextIsolation Hacer que los scripts de la página y las API de Preload se ejecuten en contextos independientes.
Preload + contextBridge Controlar explícitamente las API de seguridad expuestas al frontend.
Content Security Policy (CSP) Restringir el origen de los scripts para prevenir XSS.
Modo sandbox Se puede activar el sandbox para aislar completamente el proceso de renderizado.
Verificar URL remota El contenido remoto no confiable debe pasar por una validación de lista blanca.

Flujo de ejecución (desde el inicio hasta el renderizado)

1、启动应用 (electron .)
     ↓
2、执行 main.js(主进程启动)
     ↓
3、app.whenReady() 触发,创建 BrowserWindow
     ↓
4、BrowserWindow 启动新的渲染进程(Chromium 实例)
     ↓
5、preload.js 先执行(在隔离上下文中)
     ↓
6、index.html 加载并显示(运行前端框架)
     ↓
7、渲染进程通过 IPC 调用主进程逻辑
     ↓
8、主进程处理请求、返回结果
     ↓
9、用户关闭窗口 → 主进程监听 → app.quit()

Modelo de múltiples ventanas principal/secundario (multiproceso en paralelo)

  • Una aplicación Electron siempre tiene soloun proceso principal;
  • pero puede tenermúltiples procesos de renderizado (cada ventana independiente);
  • La comunicación entre ellos debe realizarse a través del proceso principal o usaripcMain.handlepara realizar un puente asíncrono.

Diagrama:

主进程 (Main)
 ├── 窗口1 → 渲染进程A (index.html)
 ├── 窗口2 → 渲染进程B (settings.html)
 └── 窗口3 → 渲染进程C (dashboard.html)

Componentes internos principales de Electron (dependencias subyacentes)

Nivel Tecnología
Capa de renderizado de interfaz Chromium(Blink + V8)
Capa de interfaz del sistema Node.js(libuv + C++ bindings)
Capa de gestión de procesos Núcleo de Electron (C++ + JavaScript)
Capa de lógica de aplicación Tu código JS / TS (Main + Renderer)

Tu código JS se ejecuta en el entorno Node encapsulado por Electron, mientras que la interfaz renderizada se ejecuta en el WebView de Chromium.

Otras extensiones