Construyendo una aplicación de Electron con create-react-app

No es necesario configurar el paquete web o "expulsar".

Recientemente construí una aplicación de Electron usando create-react-app . No necesitaba perder el tiempo con Webpack o “expulsar” mi aplicación tampoco. Te explicaré cómo logré esto.

Me atrajo la idea de usar create-react-app porque oculta los detalles de configuración del paquete web. Pero mi búsqueda de guías existentes para usar Electron y create-react-app juntas no dio ningún resultado, así que me sumergí y lo descubrí yo mismo.

Si se siente impaciente, puede sumergirse y mirar mi código. Aquí está el repositorio de GitHub para mi aplicación.

Antes de comenzar, déjame contarte sobre Electron y React, y por qué create-react-app es una herramienta tan buena.

Electrón y reaccionar

React es el marco de visualización de JavaScript de Facebook.

Una biblioteca de JavaScript para construir interfaces de usuario - React

Una biblioteca de JavaScript para construir interfaces de usuariofacebook.github.io

Y Electron es el marco de GitHub para crear aplicaciones de escritorio multiplataforma en JavaScript.

Electrón

Cree aplicaciones de escritorio multiplataforma con JavaScript, HTML y CSS. electron.atom.io

La mayoría usa webpack para la configuración necesaria para el desarrollo de React. webpack es una herramienta de configuración y compilación que la mayoría de la comunidad React ha adoptado sobre alternativas como Gulp y Grunt.

La sobrecarga de configuración varía (más sobre esto más adelante), y hay muchos generadores de aplicaciones y plantillas disponibles, pero en julio de 2016 Facebook Incubator lanzó una herramienta,crear-reaccionar-aplicación . Oculta la mayor parte de la configuración y permite al desarrollador usar comandos simples, como npm starty npm run buildpara ejecutar y crear sus aplicaciones.

¿Qué es la expulsión y por qué quiere evitarla?

create-react-app hace ciertas suposiciones sobre una configuración típica de React. Si estas suposiciones no son para usted, existe una opción para expulsar una aplicación ( npm run eject). Expulsar una aplicación copia toda la configuración encapsulada de create-react-app a su proyecto, proporcionando una configuración estándar que puede cambiar como desee.

Pero este es un una forma viaje. No puede deshacer la expulsión y regresar. Ha habido 49 lanzamientos (a partir de esta publicación) de create-react-app, cada uno con mejoras. Pero para una aplicación expulsada, tendrá que renunciar a estas mejoras o averiguar cómo aplicarlas.

Una configuración expulsada tiene más de 550 líneas que abarcan 7 archivos (a partir de esta publicación). No lo entiendo todo (bueno, la mayor parte, en realidad) y no quiero.

Metas

Mis objetivos son simples:

  • evitar expulsar la aplicación React
  • minimizar el pegamento para que React y Electron trabajen juntos
  • preservar los valores predeterminados, suposiciones y convenciones hechas por Electron y create-react-app / React. (Esto puede facilitar el uso de otras herramientas que asumen / requieren tales convenciones).

Receta básica

  1. ejecutar create-react-apppara generar una aplicación React básica
  2. correr npm install --save-dev electron
  3. agregar main.jsde electron-quick-start(le cambiaremos el nombre a electron-starter.js, para mayor claridad)
  4. modificar la llamada a mainWindow.loadURL(in electron-starter.js) para usar localhost:3000(webpack-dev-server)
  5. agregar una entrada principal package.jsonparaelectron-starter.js
  6. agregar un objetivo de carrera para iniciar Electron para package.json
  7. npm start seguido por npm run electron

Los pasos 1 y 2 son bastante sencillos. Aquí está el código para los pasos 3 y 4:

const electron = require('electron'); // Module to control application life. const app = electron.app; // Module to create native browser window. const BrowserWindow = electron.BrowserWindow; const path = require('path'); const url = require('url'); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow; function createWindow() { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. mainWindow.loadURL('//localhost:3000'); // Open the DevTools. mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null }) } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', createWindow); // Quit when all windows are closed. app.on('window-all-closed', function () { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit() } }); app.on('activate', function () { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow() } }); // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here.

(Esencia)

Y para los pasos 5 y 6:

{ "name": "electron-with-create-react-app", "version": "0.1.0", "private": true, "devDependencies": { "electron": "^1.4.14", "react-scripts": "0.8.5" }, "dependencies": { "react": "^15.4.2", "react-dom": "^15.4.2" }, "main": "src/electron-starter.js", "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", "electron": "electron ." } }

(Esencia)

Cuando ejecute los comandos npm en el paso 7, debería ver esto:

Puede realizar cambios en vivo en el código React y debería verlos reflejados en la aplicación Electron en ejecución.

Esto funciona bien para el desarrollo, pero tiene dos defectos:

  • la producción no usará webpack-dev-server. Necesita usar el archivo estático de la construcción del proyecto React
  • (pequeña) molestia para ejecutar ambos comandos npm

Especificación de loadURL en producción y desarrollo

En desarrollo, una variable de entorno puede especificar la URL de mainWindow.loadURL(in electron-starter.js). Si existe la var env, la usaremos; de lo contrario, usaremos el archivo HTML estático de producción.

Agregaremos un objetivo de ejecución npm (a package.json) de la siguiente manera:

"electron-dev": "ELECTRON_START_URL=//localhost:3000 electron ."

Actualización: los usuarios de Windows deberán hacer lo siguiente: (gracias a @bfarmilo)

”electron-dev”: "set ELECTRON_START_URL=//localhost:3000 && electron .”

En electron-starter.js, modificaremos la mainWindow.loadURLllamada de la siguiente manera:

const startUrl = process.env.ELECTRON_START_URL || url.format({ pathname: path.join(__dirname, '/../build/index.html'), protocol: 'file:', slashes: true }); mainWindow.loadURL(startUrl);

(Esencia)

Hay un problema con esto: create-react-app(por defecto) construye un index.htmlque usa rutas absolutas. Esto fallará al cargarlo en Electron. Afortunadamente, hay una opción de configuración para cambiar esto: establecer una homepagepropiedad en package.json. (La documentación de Facebook sobre la propiedad está aquí).

Entonces podemos establecer esta propiedad en el directorio actual y la npm run buildusaremos como una ruta relativa.

"homepage": "./",

Uso de Foreman para administrar procesos de reacción y electrones

Por conveniencia, prefiero no

  1. iniciar / administrar tanto el servidor de desarrollo React como los procesos de Electron (prefiero tratar con uno)
  2. espere a que se inicie el servidor de desarrollo React y luego inicie Electron

Foremen es una buena herramienta de gestión de procesos. Podemos agregarlo,

npm install --save-dev foreman

y agregue lo siguiente Procfile

react: npm startelectron: npm run electron

(Esencia)

Eso trata de (1). Para (2), podemos agregar un script de nodo simple ( electron-wait-react.js) que espera a que se inicie el servidor de desarrollo React y luego inicia Electron.

const net = require('net'); const port = process.env.PORT ? (process.env.PORT - 100) : 3000; process.env.ELECTRON_START_URL = `//localhost:${port}`; const client = new net.Socket(); let startedElectron = false; const tryConnection = () => client.connect({port: port}, () => { client.end(); if(!startedElectron) { console.log('starting electron'); startedElectron = true; const exec = require('child_process').exec; exec('npm run electron'); } } ); tryConnection(); client.on('error', (error) => { setTimeout(tryConnection, 1000); });

(Esencia)

NOTA: Foreman compensará el número de puerto por 100 para procesos de diferentes tipos. (Vea aquí.) Entonces, electron-wait-react.jsresta 100 para establecer el número de puerto del servidor de desarrollo React correctamente.

Ahora modifique el Procfile

react: npm startelectron: node src/electron-wait-react

(Esencia)

Finalmente, cambiaremos los objetivos de ejecución package.jsonpara reemplazarlos electron-devcon:

"dev" : "nf start"

Y ahora, podemos ejecutar:

npm run dev
ACTUALIZACIÓN (25/1/17): agregué la siguiente sección en respuesta a algunos comentarios de los usuarios (aquí y aquí). Necesitan acceder a Electron desde la aplicación de reacción y un simple requerimiento o importación arroja un error. Anoto una solución a continuación.

Accediendo a Electron desde la aplicación React

Una aplicación de Electron tiene dos procesos principales: el host / contenedor de Electron y su aplicación. En algunos casos, le gustaría acceder a Electron desde su aplicación. Por ejemplo, es posible que desee acceder al sistema de archivos local o utilizar Electron's ipcRenderer. Pero si hace lo siguiente, obtendrá un error

const electron = require('electron') //or import electron from 'electron';

Existe cierta discusión sobre este error en varios problemas de GitHub y Stack Overflow, como este. La mayoría de las soluciones proponen cambios en la configuración del paquete web, pero esto requeriría expulsar la aplicación.

Sin embargo, existe una solución / truco simple.

const electron = window.require('electron');
const electron = window.require('electron'); const fs = electron.remote.require('fs'); const ipcRenderer = electron.ipcRenderer;

Terminando

Para mayor comodidad, aquí hay un repositorio de GitHub que tiene todos los cambios anteriores, con etiquetas para cada paso. Pero, no es mucho trabajo arrancar una aplicación Electron que usa create-react-app. (Esta publicación es mucho más larga que el código y los cambios que necesitaría para integrar los dos).

Y si está utilizando create-react-app, es posible que desee consultar mi publicación, Pruebas de depuración en WebStorm y create-react-app.

Gracias por leer. Puedes ver más de mis publicaciones en justideas.io

ACTUALIZACIÓN (2/2/17). Un lector, Carl Vitullo, sugirió usar en npm startlugar de npm run devy envió una solicitud de extracción con los cambios, en GitHub. Estos ajustes están disponibles en esta rama.