Crear una aplicación de escritorio con Electron.js puede ser muy simple o muy complejo. Si queremos usar React por ejemplo seguramente vamos a terminar usando Babel y si usamos Babel seguro vamos a querer usar Wepback. Esto puede terminar en muchas configuraciones que hay que realizar, lo cual puede ser muy tedioso.
Con Next.js podemos hacer aplicaciones web con React sin configurar nada (o casi nada). Entonces, ¿por qué no hacer algo similar en Electron? Podemos combinar estas tecnologías para hacer eso mismo. Veamos cómo con un pequeño proyecto.
Lo primero que vamos a hacer es iniciar nuestro proyecto, para eso vamos a usar el comando:
yarn init --yes
Luego de generar el package.json
vamos a instalar estas dependencias:
yarn add -E electron next react react-dom
Luego vamos a agregar estos scripts a nuestro package.json
:
"scripts": {
"dev": "electron index.js",
"build": "next build",
"start": "NODE_ENV=production electron index.js"
}
De esta forma vamos a poder usar yarn dev
para correr nuestra aplicación en modo desarrollo, yarn build
para generar nuestro código listo para producción y yarn start
para correr la aplicación en modo producción usando los archivos generados antes (esto no es para generar el binario de Electron).
Electron usa dos tipos de procesos, el principal (main) y la UI (renderer). Empecemos a crear el código del proceso main.
// cargamos los módulos de electron que vamos a usarconst { app, BrowserWindow, ipcMain } = require('electron');
// cargamos la función para crear un servidor HTTPconst { createServer } = require('http');
// cargamos Next.jsconst next = require('next');
// verificamos si estamos en modo desarrolloconst dev = process.env.NODE_ENV !== 'production';
// crearmos nuestra app de Next.jsconst nextApp = next({ dev });
// obtenermos la función para manejar peticiones a Next.jsconst handler = nextApp.getRequestHandler();
// en esta variable vamos a guardar la instancia de nuestra UIlet win;
functioncreateWindow() {
// iniciamos la app de Next.js
nextApp
.prepare()
.then(() => {
// una vez lista creamos un servidor HTTP que use nuestro// handler para ruteas todas las peticiones HTTP// que reciba Next.jsconst server = createServer((req, res) => {
// si recibimos una petición por fuera de Electron// respondemos con un 404if (req.headers['user-agent'].indexOf('Electron') === -1) {
res.writeHead(404);
res.end();
return;
}
res.setHeader('Access-Control-Request-Method', 'GET');
// solo permitimos peticiones GETif (req.method !== 'GET') {
res.writeHead(405);
res.end('Method Not Allowed');
return;
}
// dejamos que Next maneje las peticionesreturn handler(req, res);
});
// empezamos a escuchar el puerto 3000 con el servidor HTTP
server.listen(3000, (error) => {
if (error) throw error;
// una vez iniciamos el servidor creamos una nueva ventana
win = new BrowserWindow({
height: 768,
width: 1024,
});
// y abrimos la URL local del servidor HTTP que creamos
win.loadURL('http://localhost:3000');
// abrimos las herramientas de desarrolloif (dev) {
win.webContents.openDevTools();
}
win.on('close', () => {
// cuando el usuario cierra la ventana borramo `win`// y paramos el servidor HTTP
win = null;
server.close();
});
});
});
}
// una vez la app esté lista iniciamos una ventana nueva
app.on('ready', createWindow)
// si todas las ventanas se cerraros matamos la aplicación
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
})l
// cuando se activa la app volvemos a abrir una ventana nueva// (algo solo para Mac)
app.on('activate', () => {
if (win === null) {
createWindow();
}
});
// acá vamos a usar el módulo `ipcMain` para recibir mensajes desde// nuestro proceso de UI y reenviar ese mensaje a quién lo envió
ipcMain.on('message', (event, message) => {
event.sender.send('message', message);
});
Ese va a ser nuestro proceso principal, con eso vamos a hacer que al abrir la aplicación se cree una nueva ventana (UI) y empecemos a usar Next.js para compilar nuestro código de React.
Ahora podemos crear nuestra primera vista de la UI, algo bastante sencillo, en una carpeta llamada pages
. En la raíz del proyecto colocamos un archivo index.js
con el siguiente código:
// importamos React.Component, Next.js por defecto importa React// para soportar JSX, así que solo necesitamos traernos Componentimport { Component } from'react';
// importamos los módulos de Electron que vamos a usarimport { ipcRenderer } from'electron';
// creamos y exportamos el componente de la homeexportdefaultclassHomePageextendsComponent{
// iniciamos el estado del componente
state = {
input: '',
message: null,
}
componentDidMount() {
// una vez el componente se monta vamos a empezar a escuchar// el evento `message` que recibimos del proceso principal// mediante el módulo `ipcRenderer`
ipcRenderer.on('message', this.handleMessage)
}
componentWillUnmount() {
// antes de desmontar el componente dejamos de escuchar// el evento `message`
ipcRenderer.removeListener('message', this.handleMessage)
}
handleMessage = (event, message) => {
// acá recibimos el mensaje del proceso **main** y lo// guardamos en el estado interno del componentethis.setState({ message })
}
handleChange = event => {
// acá guardamos el contenido de un input en el estadothis.setState({ input: event.target.value })
}
handleSubmit = event => {
// acá evitamos el submit del formulario y enviamos// al proceso **main** el contenido del input
event.preventDefault();
ipcRenderer.send('message', this.state.input)
}
render() {
// por último hacemos render de un H1, si hay un mensaje// desde el proceso **main** lo mostramos, luego el formulario// y por último estilizamos el H1.return (
<div><h1>Hola Electron!</h1>
{this.state.message &&
<p>{this.state.message}</p>
}
<formonSubmit={this.handleSubmit}><inputtype="text"onChange={this.handleChange} /></form><stylejsx>{`
h1 {
color: red;
font-size: 50px;
}
`}</style></div>
)
}
}
Con esto ya tenemos nuestra primer vista. Si ahora tratamos de iniciar nuestra aplicación con yarn dev
, vamos a ver que al entrar nos da un error. Esto es porque por defecto Next.js no soporta Electron y por lo tanto no se pueden usar los módulos de éste como ipcRenderer
.
Vamos a solucionar esto creando un pequeño archivo llamado next.config.js
en la raíz de nuestro proyecto con este contenido:
// en este archivo vamos module.exports = {
webpack(config) {
config.target = 'electron-renderer';
return config;
},
};
Este archivo nos permite modificar o extender la configuración interna de Webpack que posee Next.js. Gracias a eso podemos configurar que Webpack genere el código para el proceso renderer de Electron.
Ahora sí, si usamos el comando yarn dev
vamos a ver como se abre nuestra aplicación de Electron, compila Next.js y se abre una ventana con la UI que renderizamos en pages/index.js
.
Si escribimos en el <input />
y luego enviamos el mensaje va a aparecer entre el título y el formulario, esto es porque el mensaje se envió mediante IPC al proceso main y de vuelta al proceso renderer (nuestra UI) y así lo pudimos mostrar.
Si corremos yarn build
podemos hacer que Next.js genere los archivos para producción. Luego simplemente al correr next start
podemos iniciar nuestra aplicación usando estos archivo pregenerados y que abra extremadamente rápido.
Como vimos podemos combinar estas dos tecnologías para crear aplicaciones de escritorio muy poderosas y de forma fácil. Luego podemos ir integrando otras tecnologías según necesitemos, como Redux, o usar el método getInitialProps
que nos da Next.js para obtener datos de algún servicio HTTP y comunicarnos con el resto del mundo.
hay que cambiar el nombre del archivo main.js por index.js o cambiar el script de inicio