Crea aplicaciones de escritorio con Electron y Next.js

Educación online efectiva

Aprende con expertos de la industria

COMPARTE ESTE ARTÍCULO Y MUESTRA LO QUE APRENDISTE

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.

Iniciar el 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).

Proceso principal

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 usar
const { app, BrowserWindow, ipcMain } = require('electron');
// cargamos la función para crear un servidor HTTP
const { createServer } = require('http');
// cargamos Next.js
const next = require('next');

// verificamos si estamos en modo desarrollo
const dev = process.env.NODE_ENV !== 'production';

// crearmos nuestra app de Next.js
const nextApp = next({ dev });
// obtenermos la función para manejar peticiones a Next.js
const handler = nextApp.getRequestHandler();

// en esta variable vamos a guardar la instancia de nuestra UI
let win;

function createWindow() {
	// 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.js
		const server = createServer((req, res) => {
			// si recibimos una petición por fuera de Electron
			// respondemos con un 404
			if (req.headers['user-agent'].indexOf('Electron') === -1) {
				res.writeHead(404);
				res.end();
				return;
			}

			res.setHeader('Access-Control-Request-Method', 'GET');

			// solo permitimos peticiones GET
			if (req.method !== 'GET') {
				res.writeHead(405);
				res.end('Method Not Allowed');
				return;
			}

			// dejamos que Next maneje las peticiones
			return 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 desarrollo
			if (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.

Proceso de la UI

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 Component
import { Component } from 'react';
// importamos los módulos de Electron que vamos a usar
import { ipcRenderer } from 'electron';

// creamos y exportamos el componente de la home
export default class HomePage extends Component {
	// 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 componente
		this.setState({ message })
	}

	handleChange = event => {
		// acá guardamos el contenido de un input en el estado
		this.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>
				}

				<form onSubmit={this.handleSubmit}>
					<input type="text" onChange={this.handleChange} />
				</form>

				<style jsx>{`
					h1 {
						color: red;
						font-size: 50px;
					}
				`}</style>
			</div>
		)
	}
}

Configurando Next.js

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.

Corriendo la aplicación

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.

Conclusiones

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.

Educación online efectiva

Aprende con expertos de la industria

COMPARTE ESTE ARTÍCULO Y MUESTRA LO QUE APRENDISTE

0 Comentarios

para escribir tu comentario

Artículos relacionados