-
Notifications
You must be signed in to change notification settings - Fork 1
S10: Electron
- Tiempo: 2h (50 + 50min)
- Fecha:
-
Objetivos de la sesión:
- Entender Electron
- Aprender a crear una aplicación Electron desde cero
- Aprender a empaquetar la app para Linux
- Introducción
- Algunas aplicaciones hechas con Electron
- Arquitectura
-
Creando una aplicación electron desde cero
- Fichero package.json inicial
- Instalando electron
- Fichero principal. Versión 1
- Módulo App: Añadiendo el evento ready (Versión 2)
- Creando la ventana principal (Versión 3)
- Desactivando el menú por defecto
- Cargando contenido web en la ventana (Versión 4)
- Nuestra primera interfaz gráfica en HTML (Versión 5)
- Activando la política de seguridad de contenido CSP (Versión 6)
- Añadiendo imágenes y estilo a la interfaz (Versión 7)
- Proceso de renderizado asociado a la interfaz (Versión 8)
- Acceso al sistema desde las aplicaciones de Electron
- Empaquetando la aplicación Electron (Versión 12)
- Autor
- Licencia
- Enlaces
Electron es un paquete para node.js que te permite crear aplicaciones de escritorio con interfaces gráficas usando las tecnologías web: HTML, Javascript y CSS. Las aplicaciones creadas son multiplataforma: Linux, macOS y Windows
Típicamente para crear aplicaciones gráficas de escritorio debes utilizar los entornos nativos de cada sistema operativo. En el caso de Linux existen bibliotecas libres y multiplataformas, como por ejemplo GTK o QT. Sin embargo, dado que ya existen tecnologías abiertas que sirven para presentar la información (html, css y javascript), ¿Por qué no usarlas para hacer los interfaces gráficos de las aplicaciones? Así no es necesario aprender ninguna biblioteca particular para un sistema operativo
Aunque nunca hayas usado Electron, en realidad sí que has utilizado aplicaciones que están hechas con Electron
- Visual code
- MS Teams
- WhatsApp Desktop
- En esta página de Electron hay muchas más...
Electrón se basa en tres elementos fundamentales:
- El motor de navegación Chromium para mostrar las interfacs gráficas (como elementos web)
- Node.js para el acceso al sistema de ficheros local y el sistema operaivo
- API para acceder a funciones nativas del sistema operativo
En las aplicaciones de Electron tenemos dos tipos de procesos:
- El proceso principal: main.js. Es el que toma el control al arrancar la aplicación y lanza los navegadores. Gestiona los procesos renderizadores
- El proceso de renderizado gestiona sólo su página web (y no tiene visibilidad del resto de elementos). Se encarga de lanzar las GUIs como páginas webs en los "navegadores", y los procesos de renderizado (render.js), uno por cada ventana
Electron incluye los módulos ipcMain e ipcRenderer para la comunicación entre ambos procesos. Cuando desde el proceso principal se quiere pintar algo en la interfaz gráfica se envía la información al proceso de renderizado
Vamos a ir paso a paso construyendo nuestra primera aplicación electron, que simplemente tendrá un botón que al apretarlo imprimirá un texto en la pantalla
Creamos un directorio de trabajo y él comenzamos escribiendo el fichero package.json
inicial, donde ponemos el nombre de nuestra APP, la descripción, la versión, el nombre del fichero principal y el comando que se debe ejecutar para arrancarla al ejecutar npm start
-
Fichero:
package.json
{
"name": "mi-electron-app",
"description": "Estas es mi primera aplicación de escritorio en Electron",
"version": "0.1.0",
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
Hasta ahora no lo habíamos hecho, pero en todas las aplicaciones hechas con Node (con o sin electron) debemos crear primero el package.json
con la información de nuestra aplicación
Electron es un paquete de Node, que se instala como cualquier otro paquete usando npm
npn i electron
Se nos instalará la última versión de electron, que en el momento de hacer esta documentación es la 18.0.1. Como este mundo avanza muy rápido, en poco tiempo ya habrá otra versión más actualizada
Al realizar la instalación, npm automáticamente incluirá la versión de Electron usada en el fichero package.json. Si lo observamos ahora tiene esta pinta:
{
"name": "mi-electron-app",
"description": "Estas es mi primera aplicación de escritorio en Electron",
"version": "0.1.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"dependencies": {
"electron": "^18.0.1"
}
}
Npm ha añadido el objeto "dependencies"
a nuestro package.json de configuración. También se habrá creado el fichero package-lock.json
que contiene información sobre los paquetes instalados. Esta información NO es para los humanos
La cadena usada para indicar la vesión es: "^18.0.1". Significa que serían válidas cualquier versión de electron iguales o superiores a la 18.0.1
, pero INFERIORES a la 19.0.0
. Puedes encontrar más información sobre la semántica de los números de versión en este enlace
Ahora creamos el fichero principal: main.js, que es el que contiene el código javascript que se ejecutará. Empezamos con el programa mínimo: cargamos el módulo electron
e imprimimos un mensaje en la consola
- Fichero: main.js (versión 1)
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
Lo ejecutamos desde el terminal con el comando npm start
:
En la consola nos aparece el mensaje Arrancando electron...
y el programa no termina. El control lo tiene electron, y para que termine hay que llamar a las funciones de su API. En esta primer versión lo interrumpimos pulsando ctrl-c
desde la consola
En el módulo electron.app se encuentran todos los eventos para el control de la aplicación. Se usan desde el proceso principal. El evento ready
nos indica que los módulos de electron se han cargado, y que está listo para ejecutar el proceso principal
Ampliamos nuestro código (versión 2) con los siguiente. Todavía no hacemos nada. Simplemente se pone una traza para comprobar que ha llegado el evento ready
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', ()=>{
console.log("Evento Ready!")
});
Lo ejecutamos. Esto es lo que vemos en la consola:
Efectivamente vemos la traza de Ready. ¡Nuestro código ya tiene el control! 😀️
La ventana principal de nuestra aplicación se crea llamando al método electron.BrowserWindow(), al que se le pasa como parámetro un objeto donde se definen las propiedades de la ventana. Nosotros le pasaremos los atributos width y height para establecer el tamaño a 600x200
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 400 //-- Altura
});
});
Esto crea una ventana en blanco, ya que todavía no hemos añadido nada. Nos aparecerá en su parte superior el menú por defecto, que tiene las opciones de File, Edit, View, Window y Help
Todavía no hace nada pero... ¡Hemos creado nuestra primera aplicación gráfica multiplataforma! 😀️
El menú por defecto que sale lo podemos eliminar llamando al método setMenuBarVisibility() del objeto win
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 400 //-- Altura
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
win.setMenuBarVisibility(false)
});
Ahora, al lanzar la aplicación nos aparecerá una ventana en blanco, de tamaño 600x400, sin ningún menú
Sin embargo, en los siguientes ejemplos dejaremos activado el menú porque utilizamos las herramientas del desarrollador, que se encuentran en la pestaña de view
Las ventanas que abrimos en Electron en realidad son... ¡Navegadores! Por lo que en ellos metemos contenido web (html, css, javascript). Estas páginas web pueden ser externas (descargadas de una URL de internet) o bien internas: nuestras propias páginas locales
En este ejemplo cargamos la página de la Escuela: https://www.urjc.es/eif
, utilizando el método win.loadURL()
- Fichero: main.js (versión 4)
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 400 //-- Altura
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
//win.setMenuBarVisibility(false)
//-- Cargar contenido web en la ventana
//-- La ventana es en realidad.... ¡un navegador!
win.loadURL('https://www.urjc.es/eif');
});
Al arrancar la app vemos la página de la escuela. El título de la ventana se ha sustituido por el proporcionado por la página web
En vez de cargar una URL externa podemos abrir un fichero html local, con nuestra interfaz gráfica de la aplicación. Esto lo hacemos con el método win.loadFile(), que carga el fichero html que le pasemos como parámetro
Creamos el fichero index.html con este contenido:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi APP (version 5)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<input type="button" value="Test" id="btn_test">
<p>Display</p>
</body>
</html>
y desde nuestro proceso main lo cargamos en la ventana principal
- Fichero main.js (versión 5)
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 400 //-- Altura
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
//win.setMenuBarVisibility(false)
//-- Cargar contenido web en la ventana
//-- La ventana es en realidad.... ¡un navegador!
//win.loadURL('https://www.urjc.es/etsit');
//-- Cargar interfaz gráfica en HTML
win.loadFile("index.html");
});
Al lanzar la aplicación con npm start
nos aparece una ventana con este contenido
¡Nuestra primera interfaz gráfica en HTML funciona! 😀️
El botón de Test no hace nada todavía, ya que no hemos añadido código javascript asociado a ese fichero HTML
En esta asignatura no nos estamos preocupando demasiado por la securidad. Nos estamos centrando en aprender los mecanismos básicos que hay en los servidores. PERO la seguridad es un tema muy importante. Y sobre todo si hacemos aplicaciones con Electron, ya que al usar tecnología web se podría sufrir un ataque que haga que se ejecute código javascript malicioso, y acceder totalmente a nuestra máquina (no sólo al navegador)
Si ejecutamos el ejemplo anterior (versión 5) y abrimos la consola del navegador dentro de las herramientas del desarrollador, veremos un aviso de seguridad (warning)
(NOTA: El contenido de la página se ve en fondo negro porque tengo activado el tema dark en Chrome)
Esta es la cabecera del mensaje:
⚠️ Electron Security Warning (Insecure Content-Security-Policy)
Nos está avisando de que no hemos establecido ninguna política de seguridad de contenidos. Y por tanto se podría inyectar código malicioso en nuestra aplicación, si sufrimos un ataque
Vamos a establecer la política de que el navegador sólo pueda mostrar y ejecutar ficheros que estén en nuestra máquina. Eso lo hacemos con la siguente cabecera en el fichero html:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
El fichero html nuevo nos queda como el siguiente:
- Fichero: index.html (version 6)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Only scripts and files from the local machine are allowed -->
<!-- It removes the security Warning: Insecure Content-Security-Policy -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
<title>Mi APP (version 6)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<input type="button" value="Test" id="btn_test">
<p>Display</p>
</body>
</html>
Ahora al abrir la consola ya NO vemos el aviso anterior
La interfaz la definimos exactamente igual que cualquier página web. Como ejemplo vamos a añadir esta imagen y un archivo de estilo
- Fichero: index.html (version 7)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Only scripts and files from the local machine are allowed -->
<!-- It removes the security Warning: Insecure Content-Security-Policy -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
<link rel="stylesheet" href="style.css">
<title>Mi APP (version 7)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<img src="logo-urjc.png" alt="Logo-URJC" width=150px>
<p> <input type="button" value="Test" id="btn_test"> </p>
<p>Display:</p>
<p id="display"></p>
</body>
</html>
- Fichero: style.ccs
h1 {
width: 93%;
padding: 20px;
background-color: lightgreen;
border-radius: 10px;
text-align: center;
}
#display {
border: 2px solid lightblue;
padding: 10px;
}
Esto es lo que aparece ahora al ejecutar la aplicación:
Por último añadimos código javascript asociado la página web de interfaz. Es el proceso de renderizado. Comenzaremos con uno muy sencillo que simplemente muestra un mensaje de texto en el display al apretar el botón de Test. Además sacará un mensaje por la consola del navegador, para comprobar que todo está funcionando bien
- Fichero index.js
console.log("Hola desde el proceso de la web...");
//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
btn_test.onclick = () => {
display.innerHTML += "TEST! ";
console.log("Botón apretado!");
}
En el fichero HTML añadimos la línea para cargar el script index.js:
- Fichero: index.html (versión 8)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Permitir sólo scripts y ficheros de la máquina local -->
<!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
<!-- Hoja de estilo de la interfaz -->
<link rel="stylesheet" href="style.css">
<!-- Cargar el proceso asociado a esta página -->
<script src="index.js" defer></script>
<title>Mi APP (version 8)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<img src="logo-urjc.png" alt="Logo-URJC" width=150px>
<p> <input type="button" value="Test" id="btn_test"> </p>
<p>Display:</p>
<p id="display"></p>
</body>
</html>
Al arrancar la aplicación y apretar el botón nos aparecerá el mensaje "TEST!" en el display
En la consola del navegador veremos los mensajes mostrados por el proceso index.js
En esta animación lo vemos en funcionamiento
¡Nuestra primera aplicación en Electron funciona!!
El proceso principal de Electron (main.js) es una aplicación nativa de node.js, por lo que se tiene acceso a TODOS los recursos de la máquina, a través de las bibliotecas de Node
Sin embargo, desde los procesos asociados a las interfaces gráficas (los procesos de renderización) NO HAY ACCESO por defecto. Es es así por razones de seguridad. Hay dos enfoques para lograrlo (que se pueden combinar si se quiere):
- Dar permisos de acceso al proceso que se ejecuta en la ventana
- Establecer canales de comunicación entre el proceso main (que tiene privilegios) y el proceso de renderizado (que no los tiene) para que intercambien información
Para poder acceder a la API de node (y por tanto al Sistema) desde la página web de la interfaz, tenemos que establecer los valores de las propiedades nodeIntegration
y contextIsolation
del objeto webPreferences
de la ventana. Esto se hace desde el proceso principal al crear la ventana con `electron.BrowserWindow()'
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
//-- Dar valores a las propiedades: altura, anchura...
//-- .........
//-- Permitir que la ventana tenga ACCESO AL SISTEMA
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
Como ejemplo vamos a ampliar nuestra aplicación de Electron desarrollada en los apartados anteriores para incluir información del sistema: Arquitectura, plataforma y directorio de trabajo. Esto se optiene a través de las propiedades y métodos de process
, que está accesible en las aplicaciones node: process.arch
, process.platform
y process.cwd()
Primero lo probamos SIN tener permisos de acceso, para ver lo que ocurre. En la interfaz añadimos un lista con los elementos que queremos obtener del sistema. Esta información se situará en los bloques span
identificados como info1
, info2
e info3
. Estos bloques pertecenen todos a la clase blue
a la que se le ha asociado el color azúl. De esta manera la información saldrá resaltada en azúl
- Fichero: index.html (versión 9)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Permitir sólo scripts y ficheros de la máquina local -->
<!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
<!-- Hoja de estilo de la interfaz -->
<link rel="stylesheet" href="style.css">
<!-- Cargar el proceso asociado a esta página -->
<script src="index.js" defer></script>
<title>Mi APP (version 9)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<img src="logo-urjc.png" alt="Logo-URJC" width=150px>
<hr>
<h2>Información:</h2>
<ul>
<li>Arquitectura: <span id="info1" class="blue"></span> </li>
<li>Plataforma: <span id="info2" class="blue"></span></li>
<li>Diretorio: <span id="info3" class="blue"></span></li>
</ul>
<hr>
<p> <input type="button" value="Test" id="btn_test"> </p>
<p>Display:</p>
<p id="display"></p>
</body>
</html>
Este es el nuevo fichero de estilo:
- Fichero: style.css (versión 9)
h1 {
width: 93%;
padding: 20px;
background-color: lightgreen;
border-radius: 10px;
text-align: center;
}
.blue {
color: blue;
}
#display {
border: 2px solid lightblue;
padding: 10px;
}
Y este es el proceso de renderización
- Fichero index.js (versión 9)
console.log("Hola desde el proceso de la web...");
//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");
//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();
btn_test.onclick = () => {
display.innerHTML += "TEST! ";
console.log("Botón apretado!");
}
Este proceso accede a la información del sistema y la inserta en los bloques span
. PERO como no tiene permisos, se produce un error y la información NO se muestra:
Si abrimos la consola del navegador veremos el error en rojo
El error nos informa de que NO conoce ningún objeto llamado process
Uncaught ReferenceError: process is not defined
Para tener acceso activamos los permisos desde el proceso principal:
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 600, //-- Altura
//-- Permitir que la ventana tenga ACCESO AL SISTEMA
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
//win.setMenuBarVisibility(false)
//-- Cargar contenido web en la ventana
//-- La ventana es en realidad.... ¡un navegador!
//win.loadURL('https://www.urjc.es/etsit');
//-- Cargar interfaz gráfica en HTML
win.loadFile("index.html");
});
Y ahora al ejecutar la aplicación YA SI que vemos la información del sistema
Los procesos principal y de renderizado se pueden comunicar utilizando los módulos electron.ipcMain
y electron.ipcRenderer
. Esta comunicación se basa en el intercambio de mensajes asíncronos, igual que en la biblioteca sokets.io que ya conocemos. El emisor envía un mensaje al receptor utilizando el método send(nombre-evento, mensaje)
al que se le pasa como argumentos primero el nombre del evento (definido por nosotros) y luego la cadena con el mensaje a mandar
El receptor se pone a la escucha del evento indicado, usando una función de retrollamada. Al llegar el mensaje se ejecuta esa función
La forma de realizarlo varía según que esta comunicación sea desde el proceso princial al de renderizado o vice-versa. Esto es debido a que sólo hay un proceso principal, pero puede haber muchos de renderizado (cada uno asociado a su ventana gráfica)
Mediante este mecanismo se puede lograr que el proceso de renderizado NO tenga que acceder al Sistema, sino que le pida la información sensible al proceso main. Esto hará que la aplicación sea más segura. No obstante, por facilidad de los ejemplos, se seguirá dando permisos al proceso de renderizado
Como ejemplo modificaremos nuestra aplicación para que el proceso principal le envíe al de renderizado un mensaje asociado al evento print
(el nombre del evento lo definimos nosotros. Podemos poner cualquiera). Al recibirlo, simplemente lo mostrará en la interfaz gráfica, en un párrafo nuevo creado para ello
- Fichero: index.html (versión 10)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Permitir sólo scripts y ficheros de la máquina local -->
<!-- Con esto se elimina el aviso de seguridad: Insecure Content-Security-Policy -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
<!-- Hoja de estilo de la interfaz -->
<link rel="stylesheet" href="style.css">
<!-- Cargar el proceso asociado a esta página -->
<script src="index.js" defer></script>
<title>Mi APP (version 10)</title>
</head>
<body>
<h1>INTERFAZ GRÁFICA</h1>
<img src="logo-urjc.png" alt="Logo-URJC" width=150px>
<hr>
<h2>Información:</h2>
<ul>
<li>Arquitectura: <span id="info1" class="blue"></span> </li>
<li>Plataforma: <span id="info2" class="blue"></span></li>
<li>Diretorio: <span id="info3" class="blue"></span></li>
</ul>
<hr>
<p>Mensajes enviados desde el proceso main:</p>
<p>Print: <span id='print' class="red"></span> </p>
<p> <input type="button" value="Test" id="btn_test"> </p>
<p>Display:</p>
<p id="display"></p>
</body>
</html>
- Fichero: style.css
h1 {
width: 93%;
padding: 20px;
background-color: lightgreen;
border-radius: 10px;
text-align: center;
}
.blue {
color: blue;
}
.red {
color: red;
}
#display {
border: 2px solid lightblue;
padding: 10px;
}
En el proceso principal enviamos el mensaje utilizando el método win.webContents.send()
:
win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
Para enviarlo esperamos primero a que la ventana esté lista, así ya sabremos que el proceso de renderizado se ha ejecutado. Esto lo hacemos escuchando el evento ready-to-show
Este es el código completo del proceso main
- Fichero: main.js (versión 10)
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 600, //-- Altura
//-- Permitir que la ventana tenga ACCESO AL SISTEMA
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
//win.setMenuBarVisibility(false)
//-- Cargar contenido web en la ventana
//-- La ventana es en realidad.... ¡un navegador!
//win.loadURL('https://www.urjc.es/etsit');
//-- Cargar interfaz gráfica en HTML
win.loadFile("index.html");
//-- Esperar a que la página se cargue y se muestre
//-- y luego enviar el mensaje al proceso de renderizado para que
//-- lo saque por la interfaz gráfica
win.on('ready-to-show', () => {
console.log("HOLA?");
win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
});
});
En el proceso de renderizado se usa el método electron.ipcRenderer.on()
para escuchar el evento y ejecutar la función de retrollamada al recibirlo
//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
console.log("Recibido: " + message);
print.textContent = message;
});
Este es el código completo:
- Fichero: index.js (version 10)
const electron = require('electron');
console.log("Hola desde el proceso de la web...");
//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");
const print = document.getElementById("print");
//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();
btn_test.onclick = () => {
display.innerHTML += "TEST! ";
console.log("Botón apretado!");
}
//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
console.log("Recibido: " + message);
print.textContent = message;
});
Al ejecutarlo vemos en rojo el mensaje que ha enviado el proceso main al de renderizado
El proceso de renderizado envía mensajes al proceso principal a través del método electron.ipcRenderer.invoke()
. Y en el proceso principal se utiliza el método electron.ipcMain.handle()
para estar a la escucha del evento y ejecutar la función de retrollamada asociada
Como ejemplo haremos que cada vez que se apriete el botón de Test de la interfaz gráfica, se le envíe la notificación test
al proceso principal y este simplemente escriba una cadena en la consola
- Fichero: index.js (version 11)
const electron = require('electron');
console.log("Hola desde el proceso de la web...");
//-- Obtener elementos de la interfaz
const btn_test = document.getElementById("btn_test");
const display = document.getElementById("display");
const info1 = document.getElementById("info1");
const info2 = document.getElementById("info2");
const info3 = document.getElementById("info3");
const print = document.getElementById("print");
//-- Acceder a la API de node para obtener la info
//-- Sólo es posible si nos han dado permisos desde
//-- el proceso princpal
info1.textContent = process.arch;
info2.textContent = process.platform;
info3.textContent = process.cwd();
btn_test.onclick = () => {
display.innerHTML += "TEST! ";
console.log("Botón apretado!");
//-- Enviar mensaje al proceso principal
electron.ipcRenderer.invoke('test', "MENSAJE DE PRUEBA: Boton apretado");
}
//-- Mensaje recibido del proceso MAIN
electron.ipcRenderer.on('print', (event, message) => {
console.log("Recibido: " + message);
print.textContent = message;
});
- Fichero: main.js (versión 11)
//-- Cargar el módulo de electron
const electron = require('electron');
console.log("Arrancando electron...");
//-- Variable para acceder a la ventana principal
//-- Se pone aquí para que sea global al módulo principal
let win = null;
//-- Punto de entrada. En cuanto electron está listo,
//-- ejecuta esta función
electron.app.on('ready', () => {
console.log("Evento Ready!");
//-- Crear la ventana principal de nuestra aplicación
win = new electron.BrowserWindow({
width: 600, //-- Anchura
height: 600, //-- Altura
//-- Permitir que la ventana tenga ACCESO AL SISTEMA
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
//-- En la parte superior se nos ha creado el menu
//-- por defecto
//-- Si lo queremos quitar, hay que añadir esta línea
//win.setMenuBarVisibility(false)
//-- Cargar contenido web en la ventana
//-- La ventana es en realidad.... ¡un navegador!
//win.loadURL('https://www.urjc.es/etsit');
//-- Cargar interfaz gráfica en HTML
win.loadFile("index.html");
//-- Esperar a que la página se cargue y se muestre
//-- y luego enviar el mensaje al proceso de renderizado para que
//-- lo saque por la interfaz gráfica
win.on('ready-to-show', () => {
win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
});
//-- Enviar un mensaje al proceso de renderizado para que lo saque
//-- por la interfaz gráfica
win.webContents.send('print', "MENSAJE ENVIADO DESDE PROCESO MAIN");
});
//-- Esperar a recibir los mensajes de botón apretado (Test) del proceso de
//-- renderizado. Al recibirlos se escribe una cadena en la consola
electron.ipcMain.handle('test', (event, msg) => {
console.log("-> Mensaje: " + msg);
});
Al ejecutarlo y darle al botón de Test vemos que en la consola aparecen los mensajes recibiso del proceso de renderizado
En esta animación lo vemos en funcionamiento
Una de las ventajas de Electron es que nos permite crear ficheros ejecutables para las diferentes plataformas (Linux, Mac, Windows) de manera sencilla. Para ello vamos a utilizar el paquete electron-builder
Lo primero que hacemos es completar el fichero package.json
añadiendo más información. En la primera parte están los datos de la aplicación: nombre, autor, descripción,... En el apartado "scripts"
hay que añadir tres nuevos, que son los necesarios para ejecutar electron-builder y generar el paquete.
En este ejemplo empaquetaremos la aplicción para Linux, generando un fichero en formato appimage. En la página de electron-builder hay información para hacer el empaquetado en el resto de plataformas.
Este es el nuevo fichero package.json:
{
"name": "mi-electron-app",
"description": "Estas es mi primera aplicación de escritorio en Electron",
"version": "0.1.0",
"main": "main.js",
"author": {
"name": "Obijuan"
},
"homepage": "",
"license": "LGPL",
"scripts": {
"start": "electron .",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"appId": "mi-electron-app-1-id",
"linux": {
"target": [
"AppImage"
],
"category": "Utility"
}
},
"devDependencies": {
"electron": "^18.0.1"
}
}
El siguiente paso es instalar electron-builder
utilizando este comando:
npm i electron-builder
Al hacerlo, veremos que nos ha metido una entrada nueva en devDependencies
en el package.json:
"devDependencies": {
"electron": "^18.0.1",
"electron-builder": "^22.14.13"
}
Para hacer el empaquetado sólo hay que ejecutar el siguiente comando:
npm run dist
En la consola aparerán estos mensajes:
El empaquetador ha creado un nuevo directorio: dist
. En él encontraremos el fichero mi-electron-app-0.1.0.AppImage
que contiene nuestra aplicación lista para ser ejecuta en cualquier Linux!
Para probarlo sólo es necesario asegurarnos que tiene permisos de ejecución y listo! Lo podemos ejecutar directamente desde el navegador de ficheros, o desde la consola
- Juan González-Gómez (Obijuan)
- S0: Presentación
- S1: Lenguajes de marcado. Markdown
- S2: Node.js
- S3: Node.js. Módulos
- S4: XML
- S5: JSON
- S6: Formularios y Cookies (13-Marzo-2023)
- S7: Peticiones AJAX (21-Marzo-2023)
- S8: NPM. Paquetes node.js (28-Marzo-2023)
- S9: Websockets (11-Abril-2023)
- S10: Electron (24-Abril-2023)
- S11: A-frame
Prácticas y sesiones de laboratorio
- L5: Datos de la tienda y JSON
- L6: Login, carrito, pedidos (14-Marzo-2023)
- L7: Búsquedas (27-Marzo-2023)
- L8: ¡Oxígeno!. Tiempo de laboratorio para que avances con tu práctica
- L11: Home chat (25-Abril-2023)
- L12: Laboratorio puro. NO hay contenido nuevo (8-Mayo-2023)