electron:
aplicaciones de escritorio

John Cardozo

John Cardozo

electron:
aplicaciones de escritorio

qué es electron?

Framework Open Source para desarrollo de aplicaciones multiplataforma

Motor de renderizado

Node.js

Chromium

Motor de ejecución

HTML

CSS

Javascript

Plataforma de Ejecución

Lenguaje de desarrollo

apps desarrolladas en electron

VS Code

Atom

Figma

Notion

Github Desktop

MongoDB Compass

Invision

Vivifyscrum

Insomnia

Postman

Wordpress

Teams

WhatsApp

Facebook Messenger

Slack

Discord

Twitch

Desarrollo

Comunicación

Proyectos y CMS

Prototipado

nodejs

Instalación

Verificación

node --version

npm --version

Node Package Manager

Node

creación del proyecto

Inicialización del proyecto

npm init -y

El flag -y contesta a todas las preguntas con "yes"

Instalación de Electron

npm i electron -D

Genera el folder node_modules

Si se está usando git se debe crear el archivo .gitignore que excluye el folder node_modules del repositorio de código

"devDependencies": {
  "electron": "^12.0.6"
}

archivo package.json

package.json - estructura del proyecto

{
  "main": "src/main.js"
}

archivo de inicio de la app

{
  "start": "electron ."
}

instrucción de ejecución

folder de librerías de Node.js

folder de código de la aplicación

folder de views = archivos html

archivo principal de interfaz

archivo principal de aplicación

archivo de exclusión de git

archivo de configuración del proyecto

package.json

hello world

// Librerías
const { app, BrowserWindow } = require("electron");
const url = require("url");
const path = require("path");

let ventanaPrincipal;

// Evento "ready" de la aplicación
app.on("ready", () => {
  // Creación de la ventana principal
  ventanaPrincipal = new BrowserWindow({});
  // Carga del archivo index.html en la ventana
  ventanaPrincipal.loadURL(
    url.format({
      pathname: path.join(__dirname, "views/index.html"),
      protocol: "file",
      slashes: true,
    })
  );
});

main.js

hello world - ejecución y carga de index.html

npm start

instrucción de ejecución

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo Manager</title>
</head>
<body>
    <h1>Tareas</h1>
    <ul>
        <li>Tarea1</li>
        <li>Tarea2</li>
        <li>Tarea3</li>
    </ul>
</body>
</html>

index.html

opcional: electron-reload

npm install electron-reload electron-reloader --save-dev

Instalación

// Electron reload en modo desarrollo
if (!app.isPackaged) {
  require('electron-reload')(__dirname, {
    electron: path.join(__dirname, 
                        'node_modules', '.bin', 'electron'),
    hardResetMethod: 'exit'
  })
  try {
    require('electron-reloader')(module, {
      debug: true,
      watchRenderer: true
    });
  } catch (_) { 
    console.log('Error'); 
  }  
}

archivo principal: main.js

menus

// Libreria
const { Menu } = require("electron");

app.on("ready", () => {
  // ...
  // Carga el menu de un template
  const menuPrincipal = 
        Menu.buildFromTemplate(templateMenu);
  Menu.setApplicationMenu(menuPrincipal);
});

Librería y carga en app.on('ready')

const templateMenu = [
  {
    label: "Tareas",
    submenu: [
      {
        label: "Nueva",
        accelerator: "Ctrl+N",
        click() {
          console.log("click");
        },
      },
    ],
  },
];

Librería y carga en app.on('ready')

menus: eliminar nombre de la aplicación

const templateMenu = [
  // ...
];

// Verifica si el SO es Mac
if (process.platform === "darwin") {
  // Elimina del menu la opción 
  // con el nombre de la app
  templateMenu.unshift({
    label: app.getName(),
  });
}

Elimina el nombre de la app

Solo en Mac

creación de una nueva ventana

let ventanaNuevaTarea;

function crearVentanaNuevaTarea() {
  // Crea la ventana de una nueva tarea
  ventanaNuevaTarea = new BrowserWindow({
    width: 400,
    height: 300,
    title: "Nueva tarea",
  });
  // Elimina el menu por defecto. SOLO WINDOWS Y LINUX
  ventanaNuevaTarea.setMenu(null);
  // Carga del archivo "nuevaTarea.html"
  ventanaNuevaTarea.loadURL(
    url.format({
      pathname: path.join(__dirname, "views/nuevaTarea.html"),
      protocol: "file",
      slashes: true,
    })
  );
}

Función que crea y muestra una nueva ventana

Se debe crear el archivo nuevaTarea.html

La función puede ser invocada desde el menú

cierre de las ventanas

app.on("ready", () => {
  // ...  
  // Escucha el evento de cierre de la ventana principal
  ventanaPrincipal.on("closed", () => {
    // Cierra la aplicación
    app.quit();
  });
});

Al cerrar la ventana principal, la ventana nueva queda abierta

function crearVentanaNuevaTarea() {
  // ...
  // Escucha el evento de cierre de la ventana
  ventanaNuevaTarea.on('closed', () => {
      // Libera la memoria de la ventana
      ventanaNuevaTarea = null;
  })
}

Al cerrar la ventana, libera la memoria

cierre de las ventanas con shortcut

const templateMenu = [
  {
    label: "Tareas",
    submenu: [
      {
        label: "Nueva tarea",
        accelerator: "Ctrl+N",
        click() {
          crearVentanaNuevaTarea();
        },
      },
      {
        label: "Salir",
        accelerator: process.platform === 'darwin' ? "command+Q" : "Ctrl+Q",
        click() {
          app.quit();
        },
      },
    ],
  },
];

Verifica el SO para ver cuál shortcut se debe asignar

activación de devtools

// Verifica que la aplicación 
// no esté empaquetada
if (!app.isPackaged) {
  // Agrega una nueva opción al menú
  templateMenu.push({
    label: "DevTools",
    submenu: [
      {
        label: "Mostrar/Ocultar Dev Tools",
        click(item, focusedWindow) {
          focusedWindow.toggleDevTools();
        },
      },
      {
        role: "reload",
      },
    ],
  });
}

Nueva opción del menú, sólo si la app no está empaquetada

estilos

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

Bootstrap

<link rel="stylesheet" href="https://bootswatch.com/4/lux/bootstrap.min.css">

Bootswatch

CSS en el proyecto

<link rel="stylesheet" href="../styles/estilo.css">

Photon

ventana: crear tarea

<div class="container p-4">
  <h1>Nueva tarea</h1>
  <form>
      <div class="form-group">
        <label for="tarea">Nombre de la tarea</label>
        <input type="text" id="tarea" 
               class="form-control" autofocus>
    </div>
    <button id="boton" 
            class="btn btn-info btn-block">Crear tarea</button>
  </form>
</div>

HTML

procesos: ipcrenderer

nuevaTarea.html

index.html

main.js

on

ipcMain

ipcRenderer

send

escucha eventos

envía eventos

1

2

ipc renderer: nuevatarea.html

<script>
  document.addEventListener("DOMContentLoaded", () => {

  const { ipcRenderer } = require('electron')

  // Obtiene el boton
  let boton = document.getElementById("boton");
  boton.addEventListener("click", (evento) => {
    // Previene el envio del formulario
    evento.preventDefault();
    // Obtiene el nombre de la tarea
    let tarea = document.getElementById("tarea").value;
    // Objeto a ser enviado
    const datos = {
      tarea: tarea
    }
    // Envio de los datos al proceso principal
    ipcRenderer.send("nueva-tarea", datos)
  })

});
</script>

nuevaTarea.html

ventanaNuevaTarea = new BrowserWindow({
  width: 400,
  height: 300,
  title: "Nueva tarea",
  webPreferences: {
    nodeIntegration: true,
    contextIsolation: false
  }    
});

main.js

ipc main: main.js

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

app.on("ready", () => {
  // ...
 
  // Escucha el evento "nueva-tarea"
  ipcMain.on("nueva-tarea", (evento, datos) => {
    
    // Envía los datos a index.html
    ventanaPrincipal.webContents.send('nueva-tarea', datos);
 
    // Cierra la ventana para crear la nueva tarea
    ventanaNuevaTarea.close();
  });
  
});      

main.js

ipc renderer: index.html

// Ejecución cuando el DOM está creado
document.addEventListener("DOMContentLoaded", () => {
  
  const { ipcRenderer } = require("electron");
  
  // Escucha el evento "nueva-tarea"
  ipcRenderer.on("nueva-tarea", (evento, datos) => {
    // Obtiene la lista de tareas
    let tareas = document.getElementById("tareas");
    
    // Crea un elemento
    let nuevoElemento = document.createElement("li");
    nuevoElemento.innerHTML = datos.tarea;
    
    // Agrega el elemento a la lista
    tareas.appendChild(nuevoElemento);
  })
});  

index.html <script>

opcional: refactor

<script>
  require('./index-renderer.js')        
</script>
  1. Extraer el código <script>
  2. Hacer un archivo por cada template
  3. Importar el archivo desde el HTML con require
<script src="./index-renderer.js" defer>
</script>

Importar con sintaxis Nodejs

Importar con sintaxis HTML

packaging

Generación de aplicación final para diferentes sistemas operativos

electron-packager

aplicación escrita en Electron

packaging: paquetes

Instalación de electron y electron-packager como devDependencies

npm install electron-packager --save-dev

Adicionar el nombre del producto a package.json

{
  "productName": "Administrador de Tareas",
  "devDependencies": {
    "electron": "^10.1.5",
    "electron-packager": "^15.1.0"
  }
}

packaging: ícono

Creación del ícono de la aplicación

Crear los archivos de ícono en el folder "assets"

/assets/icon.icns

/assets/icon.ico

/assets/icon.png

Se puede descargar una imagen png transparente y transformarla a otros formatos en sitios web online

i

packaging: scripts

Adición de script

Modificar el archivo package.json

"scripts": {
  "start": "electron .",
  "package-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=assets/icon.icns --prune=true --out=release",
  "package-win": "electron-packager . --overwrite --platform=win32 --arch=ia32 --icon=assets/icon.ico --prune=true --out=release",
  "package-linux": "electron-packager . --overwrite --platform=linux --arch=x64 --icon=assets/icon.png --prune=true --out=release"
}

npm run package-mac

npm run package-win

npm run package-linux

Generación del paquete

Adicionar el folder release al archivo .gitignore

!

release/

john cardozo

johncardozo@gmail.com

Electron

By John Cardozo

Electron

Creación de proyecto, ejecución y carga de HTML, CSS, menús, shortcuts, DevTools, BrowserWindow, procesos: ipcMain y ipcRenderer, eventos (send, on) y packaging.

  • 790