Electron

Nombre original Atom Shell.
Desde Electron 28, se soporta ESM.
Referencia: https://github.com/Elanis/web-to-desktop-framework-comparison
NW.js (anteriormente node-webkit) es el principal competidor de Electron, ambos son soluciones de aplicaciones de escritorio multiplataforma basadas en una arquitectura Chromium+Node.js.
NW.js se fundó 2 años antes que Electron, pero Electron es ahora mucho más popular que NW.js, y es poco probable que la situación cambie en el futuro.
  • Electron separa el proceso Principal (Main) y el proceso de Renderizado (Renderer).
    El proceso de Renderizado se divide en dos contextos, ejecutando código Node.js y código del lado del navegador, respectivamente.
El modelo de Electron separa las preocupaciones, pero la utilidad de este diseño de separación forzada de preocupaciones es muy discutible.
El diseño de IPC excepcionalmente engorroso y las continuas "demostraciones de seguridad" hacen que la experiencia de desarrollo sea extremadamente deficiente.
  • NW.js se basa en la arquitectura de Chrome Apps, donde Node.js y el renderizador de Chromium comparten el mismo proceso, lo que permite el uso directo de módulos Node.js en el lado del navegador.
    Dado que los plugins nativos de Node.js se pueden cargar directamente en el lado del navegador, el rendimiento de NW.js es muy superior al de Electron, que depende de IPC, y a las soluciones basadas en WASM.
Existen dos modos: contexto separado y contexto mixto:
  • En el modo de contexto separado, hay dos entornos: contexto de Node y contexto Web. El contexto de Node es el entorno de ejecución para los módulos de Node.js, y el contexto Web es el entorno de ejecución para los scripts de NW.js.
    Debido al aislamiento de las variables globales en diferentes contextos, pueden surgir fácilmente problemas de igualdad de referencia.
  • En el modo de contexto mixto, solo se utiliza un contexto.
    No se producen problemas de igualdad de referencia, pero existe el riesgo de que el contexto sea contaminado por bibliotecas de terceros.
  • El punto de entrada de Electron es el script JavaScript del proceso Principal.
  • El punto de entrada de NW.js es principalmente un archivo HTML, pero también puede ser un script JavaScript.
La documentación de Electron es significativamente mejor que la de NW.js; la documentación de esta última tiene una legibilidad deficiente y carece gravemente de actualizaciones.
  • Electron no tiene protección nativa del código fuente; requiere el uso de soluciones de bytecode V8 como bytenode o electron-vite.
  • NW.js tiene un compilador de código proporcionado oficialmente nwjc, que funciona con el mismo principio que bytenode pero con ligeras diferencias de implementación:
    Como NW.js puede modificar directamente V8, evita muchas limitaciones presentes en soluciones externas como bytenode.
  • Electron tiene muchos usuarios de empresas comerciales; el proyecto más famoso es VSCode.
  • A pesar de su inicio anterior, NW.js tiene una base de usuarios relativamente pequeña.
    Los juegos creados con RPG Maker son probablemente la mayor fuente de usuarios de NW.js en la actualidad.
Neutralinojs no empaqueta Chromium; en su lugar, utiliza el WebView del sistema operativo.
WebView, como era de esperar, trae problemas de compatibilidad y, por lo tanto, no se consideraría una solución de nivel de producción.
Neutralinojs no soporta sistemas Windows más antiguos: https://github.com/neutralinojs/neutralinojs/issues/486
Tauri utiliza Rust y el WebView del sistema operativo, no soporta módulos Node.js y requiere que las API se escriban en Rust.
WebView, como era de esperar, trae problemas de compatibilidad y, por lo tanto, no se consideraría una solución de nivel de producción.
Además de las plataformas de escritorio, Tauri también planea soportar iOS y Android.
  • El aislamiento de contexto crea una complejidad innecesaria.
  • La API carece de uniformidad; existen múltiples formas de eliminar la barra de menú de una ventana.
  • Estrategia de iteración de versiones principales excesivamente rápida.
  • Falta de frameworks de prueba útiles.
El proceso principal de una aplicación Electron, que es el punto de entrada de toda la aplicación; solo puede haber una instancia.
El proceso responsable de la interfaz de usuario en una aplicación Electron, iniciado por el proceso principal; puede haber múltiples instancias.
Los procesos de renderizado, por defecto, no tienen acceso directo a los módulos nativos de Node.js.
Todos los módulos de Node.js deben ser invocados indirectamente a través de interfaces aisladas por contexto creadas por scripts de precarga (preload scripts).
La integración de Node.js en el proceso de renderizado se puede habilitar estableciendo la opción nodeIntegration: true al crear la ventana.
Por defecto, el script de precarga está aislado del entorno host del proceso de renderizado.
Ambos solo pueden comunicarse a través de contextBridge y window.postMessage.
Este comportamiento se puede cancelar estableciendo contextIsolation: false.
contextBirdge.exposeInMainWorld es la única forma de exponer APIs desde un script de precarga al entorno host del proceso de renderizado.
Los tipos de datos disponibles para el puenteo son limitados:
Además de los tipos básicos estructuralmente clonables, también se permite enviar funciones, Promesas, errores y otros objetos, pero aún no satisface las necesidades comunes.
El puenteo también tiene las siguientes características:
  • Para la precarga, la exposición de la API es única y no se puede repetir; los cambios en la API tampoco se pueden sincronizar con el entorno host.
  • Para el entorno host, las funciones remotas aparecerán como código nativo.
  • Los objetos expuestos se congelan, por lo que para el entorno host, la API no es escribible.
  • Para tipos no soportados, pueden ocurrir tres resultados diferentes: pasar undefined, copia de propiedades de objeto o un error.
El proceso de renderizado puede usar ipcRenderer para enviar solicitudes al proceso principal.
Este IPC soporta dos modos:
  • send: solo envía mensajes a ipcMain
  • invoke: modo de solicitud-respuesta
Debido al aislamiento de contexto, llamar a ipcRenderer.send/invoke en el proceso de renderizado es muy inconveniente.
El proceso principal puede enviar solicitudes al proceso de renderizado, pero solo si primero obtiene el objeto WebContents del proceso de renderizado.
El objeto WebContents se puede obtener de dos maneras:
  • Si el renderizador envía primero una solicitud al proceso principal, el event.sender recibido por el proceso principal es el objeto WebContents.
  • El proceso principal mantiene el BrowserWindow cuando crea la ventana, y luego usa BrowserWindow.webContents.
Debido al aislamiento de contexto, registrar escuchadores de eventos en el proceso de renderizado es muy inconveniente.
Interfaz estándar MessagePort.
https://www.electronjs.org/docs/latest/tutorial/message-ports
Adecuado para la comunicación entre main y renderer, y renderer y renderer.
Dado que main y renderer pueden comunicarse a través de ipcMain e ipcRenderer,
este método de comunicación se utiliza principalmente para la comunicación entre renderer y renderer.
Antes de establecer la comunicación, los objetos MessagePort deben pasarse primero a través de ipcMain e ipcRenderer.
Se puede lograr IPC utilizando protocolos de red, como HTTP y WebSocket.
Considerando lo engorroso que es establecer la comunicación entre Main y Renderer (contexto Web) en Electron,
utilizar IPC basado en protocolos de red para reducir la complejidad es también un método.
  • Las interfaces IPC se expondrán a otros programas en la misma red.
  • El rendimiento es peor que otros métodos.
Debido a diferentes ABIs, Electron no puede usar directamente los módulos nativos de Node.js; los módulos deben compilarse específicamente para Electron.
https://www.electronjs.org/docs/latest/tutorial/devtools-extension
Electron tiene un soporte limitado para la API de extensiones de Chrome y puede cargar algunas herramientas de desarrollo.
En Windows y macOS, la API incorporada de Electron se puede utilizar para configurar el inicio al arrancar:
https://www.electronjs.org/docs/latest/api/app#appsetloginitemsettingssettings-macos-windows
Sin embargo, la implementación de las funciones relacionadas está llena de errores lamentables:
  • https://github.com/electron/electron/issues/32657
  • https://github.com/electron/electron/issues/33308
Esta implementación no soporta argumentos personalizados.
Una solución específicamente para persistir elementos de configuración como archivos JSON.
Internamente utiliza el módulo conf del mismo autor, soportando JSON Schema y migración de versiones basada en package.json.
Una base de datos de documentos en memoria que persiste intermitentemente en el sistema de archivos.
Desarrollo no activo.
Un módulo de JavaScript puro que utiliza JSON como base de datos.
Esta cosa es básicamente una clase para leer y escribir JSON, sin características destacadas.
El autor dejó de mantenerlo.
Una base de datos avanzada que combina RxJS y PouchDB.
Debido al uso del ecosistema de PouchDB, soporta oficialmente Electron.
Un ejemplo típico de descargas desproporcionadas en relación con las estrellas de Github; la base de usuarios real no es grande.
A pesar de los esfuerzos significativos del autor en este proyecto, todavía tiene una curva de aprendizaje pronunciada para los nuevos usuarios.
Este proyecto también depende de demasiados paquetes que el autor no puede controlar, lo que hace que el proyecto sea muy frágil:
  • RxJS
  • PouchDB
  • pouchdb-find (el método find de RxCollection usa directamente este paquete, compartiendo así los mismos defectos)
  • mquery (código copiado)
A partir de la versión v10, RxDB comenzó a prepararse para alejarse de PouchDB.
Considerando la popularidad del proyecto, la mantenibilidad y la fiabilidad de sus dependencias, no vale la pena usar RxDB.
En Node.js, PouchDB utiliza LevelDB como su backend de almacenamiento a través del módulo levelup.
El backend del módulo levelup es un módulo nativo, pero soporta Electron de forma nativa, por lo que se puede usar sin electron-rebuild.
PouchDB no está diseñado para escenarios multiproceso; tiene muchas optimizaciones basadas en memoria, y su backend Node.js LevelDB tampoco soporta multiproceso.
Por lo tanto, cuando se usa en Electron, su instancia solo debe crearse en el proceso Principal, y luego exponerse al Renderizador para su acceso a través de IPC.
pouchdb-find es una extensión oficial para implementar la sintaxis de consulta estilo Mongo.
Este paquete tiene muchos problemas, incluyendo, entre otros:
  • Resultados incorrectos incluso con índices:
    https://github.com/pouchdb/pouchdb/issues/7810
    Aunque el problema relacionado se cerró, sigo encontrando el mismo problema en el paquete v7.2.2.
  • Descarte de selectores:
    https://github.com/pouchdb/pouchdb/issues/6572
    https://github.com/pouchdb/pouchdb/issues/6371
Confiar en este paquete para las consultas es claramente poco fiable.
Sin soporte de Mongo, PouchDB no tiene valor práctico.
La última versión se lanzó en 2020; los errores durante este período no se han corregido, y muchos problemas críticos han sido ignorados por los mantenedores.
Su definición de tipo @types/pouchdb también es antigua (adaptada para PouchDB v6.4).
El autor de RxDB también considera que PouchDB no está bien mantenido en las notas de la versión v10 de RxDB.
https://github.com/pouchdb/pouchdb/issues/3224
Como módulo nativo que no soporta Node-API (N-API),
better-sqlite3 necesita ser procesado con electron-rebuild antes de poder ser usado, lo que puede llevar a problemas de bloqueo de dependencias.
Este es el módulo SQLite3 mantenido por el equipo de Ghost.
Este módulo ha sido reescrito basándose en Node-API desde la versión v5.
La interfaz de callback también establece `this`, lo que imposibilita el uso de funciones de flecha.
Para obtener el envoltorio Promise, es necesario instalar un envoltorio de terceros node-sqlite.
Cada instancia de Database en esta biblioteca es solo una conexión, y no hay condiciones de carrera.
Durante el inicio de una transacción, todas las sentencias ejecutadas en la instancia se convierten en parte de esa transacción.
Por lo tanto, siempre es necesario crear una nueva instancia para iniciar una transacción. Para reutilizar instancias, esto implica crear un pool, lo que complica la solución.
Esta es la biblioteca SQLite3 utilizada por VSCode, que es un fork de sqlite3.
https://www.electronjs.org/docs/latest/tutorial/application-distribution
Esencialmente, la distribución de aplicaciones se logra distribuyendo paquetes binarios preconstruidos de Electron que cumplen una estructura de directorio específica.
Por ejemplo, en Windows, el método de distribución manual consiste en descargar el paquete binario de Electron preconstruido para la versión y plataforma correspondientes,
crear un directorio resources/app debajo del directorio de Electron, y luego copiar todos los archivos del proyecto a este directorio.
Posteriormente, suele ser necesario renombrar el ejecutable de Electron y cambiar su icono.
El paquete oficial electron-packager se puede utilizar para simplificar los pasos anteriores.
Un formato de archivo similar a tar, específico de Electron, diseñado para empaquetar proyectos de Electron en un solo archivo.
El archivo resources/app.asar puede reemplazar la carpeta resources/app.
Algunos módulos nativos mostrarán este error después de ser empaquetados en un archivo asar.
Sin usar el empaquetado asar, Electron puede encontrar y cargar correctamente los módulos nativos.
Usar --asar.unpack=*.{node,dll} en lugar de --asar de electron-packager puede resolver este problema.
Referencias:
  • https://github.com/electron/packager/issues/424
  • https://github.com/electron/asar/issues/75
app.isPackaged se usa ampliamente para determinar si una aplicación Electron está en un entorno de desarrollo o producción.
Sin embargo, su método de juicio es bastante contraintuitivo: comprueba si el nombre del archivo ejecutable de Electron sigue siendo "electron".
Este paquete automatiza la creación de aplicaciones para diversas plataformas.
  • Soporta la configuración de iconos de archivos ejecutables.
  • Soporta la eliminación de devDependencies para reducir el tamaño del paquete.
electron-packager carece de la característica para ignorar rutas de paquete mediante un archivo de ignorados. Aunque hubo una PR para un archivo .electronignore, fue rechazada
(la razón del rechazo carecía de lógica y base fáctica).
La falta de esta característica convierte a electron-packager en un paquete inútil, porque a menos que se añada una gran cantidad de parámetros de ignorar en la línea de comandos, el entorno de desarrollo se empaquetará junto.
Cuando se habilita la opción --all, el uso de recursos de E/S y CPU de este proyecto es significativamente mayor de lo requerido para las tareas que realiza.
Después de revisar el código fuente, se descubrió que el programa simplemente paraleliza todas las tareas, lo que en Electron v28 significa descargar 10 versiones de Electron simultáneamente.
Considerando el ancho de banda del servidor y el ancho de banda del descargador, este paralelismo desenfrenado solo aumenta la probabilidad de encontrar errores de red.
Solución de código abierto para instaladores de Windows.
Soportado por electron-builder.
Soportado por el paquete de la comunidad electron-wix-msi.
Formato UWP soportado desde Windows 8, se puede publicar en Microsoft Store.
electron-builder puede usar la herramienta MakeAppx para empaquetar programas como appx.
El formato principal de Microsoft soportado desde 2018, destinado a reemplazar MSI, para sistemas Windows 10 y superiores, se puede publicar en Microsoft Store.
  • La comunidad de Electron aún no tiene una solución de empaquetado MSIX.
  • El proveedor de soluciones de empaquetado Advanced Installer soporta el empaquetado de proyectos Electron como MSIX.
  • La herramienta oficial de empaquetado MSIX de Microsoft se puede usar para reempaquetar instaladores EXE o MSI como MSIX:
    https://docs.microsoft.com/en-us/windows/msix/packaging-tool/create-app-package
Electron tenía una biblioteca de pruebas basada en ChromeDriver mantenida oficialmente, Spectron, pero finalmente fue descontinuada a principios de 2022 debido a la falta de mantenedores.
Una alternativa potencial a Spectron es Playwright.
Sin embargo, hasta Playwright v1.16, las pruebas basadas en Playwright siguen siendo una característica experimental y en gran medida no disponibles:
  • No se pueden depurar errores
  • `evaluate` no soporta la importación de módulos Node.js
Una biblioteca para crear capas superpuestas de ventana.
Desarrollo y empaquetado integral de aplicaciones Electron.
Empaquetado y distribución integral de aplicaciones Electron.
La documentación es inconsistente hasta un punto lamentable.
Electron utiliza una versión de ABI diferente a la de Node.js, por lo que los módulos nativos descargados por npm y yarn dirigidos a la versión actual de Node.js no se pueden usar directamente.
electron-rebuild es una herramienta para resolver este problema.
Por razones de mantenibilidad, no se recomienda usar módulos nativos que requieran electron-rebuild.
  • electron-rebuild no puede seleccionar la versión de ABI correcta, lo que significa que los módulos que dependen de electron-rebuild pueden convertirse en una dependencia difícil de actualizar.
    https://github.com/electron/electron-rebuild/issues/886
  • Aunque pertenece al repositorio oficial, electron-rebuild a menudo no se mantiene al día con las últimas versiones de Electron.