Créer une application Electron avec create-react-app

Aucune configuration Webpack ou «éjection» nécessaire.

J'ai récemment créé une application Electron en utilisant create-react-app . Je n'avais pas non plus besoin de me débrouiller avec Webpack ou d '«éjecter» mon application. Je vais vous expliquer comment j'ai accompli cela.

J'ai été attiré par l'idée d'utiliser create-react-app car il cache les détails de configuration du webpack. Mais ma recherche de guides existants pour utiliser Electron et create-react-app ensemble n'a pas porté ses fruits, alors j'ai juste plongé et compris moi-même.

Si vous vous sentez impatient, vous pouvez plonger directement et regarder mon code. Voici le dépôt GitHub pour mon application.

Avant de commencer, laissez-moi vous parler d'Electron et de React, et pourquoi create-react-app est un si bon outil.

Electron et React

React est le framework d'affichage JavaScript de Facebook.

Une bibliothèque JavaScript pour créer des interfaces utilisateur - React

Une bibliothèque JavaScript pour créer des interfaces utilisateurfacebook.github.io

Et Electron est le cadre de GitHub pour créer des applications de bureau multiplateformes en JavaScript.

Électron

Créez des applications de bureau multiplateformes avec JavaScript, HTML et CSS. electron.atom.io

La plupart utilisent webpack pour la configuration nécessaire au développement de React. webpack est un outil de configuration et de construction que la plupart de la communauté React a adopté sur des alternatives telles que Gulp et Grunt.

La surcharge de configuration varie (plus à ce sujet plus tard), et il existe de nombreux générateurs standard et d'application disponibles, mais en juillet 2016, Facebook Incubator a publié un outilcreate-react-app . Il cache la plupart de la configuration et permet au développeur d'utiliser des commandes simples, telles que npm startet npm run buildpour exécuter et créer leurs applications.

Qu'est-ce que l'éjection et pourquoi voulez-vous l'éviter?

create-react-app fait certaines hypothèses sur une configuration React typique. Si ces hypothèses ne vous conviennent pas, il existe une option pour éjecter une application ( npm run eject). Éjecter une application copie toute la configuration encapsulée de create-react-app dans votre projet, fournissant une configuration standard que vous pouvez modifier à votre guise.

Mais c'est un aller simple. Vous ne pouvez pas annuler l'éjection et revenir en arrière. Il y a eu 49 versions (à partir de cet article) de create-react-app, chacune apportant des améliorations. Mais pour une application éjectée, vous devrez soit renoncer à ces améliorations, soit déterminer comment les appliquer.

Une configuration éjectée est plus de 550 lignes couvrant 7 fichiers (à partir de ce post). Je ne comprends pas tout (enfin, la plupart, en fait) et je ne veux pas.

Buts

Mes objectifs sont simples:

  • éviter d'éjecter l'application React
  • minimiser la colle pour que React et Electron travaillent ensemble
  • conserver les valeurs par défaut, les hypothèses et les conventions faites par Electron et create-react-app / React. (Cela peut faciliter l'utilisation d'autres outils qui supposent / nécessitent de telles conventions.)

Recette de base

  1. exécuter create-react-apppour générer une application React de base
  2. courir npm install --save-dev electron
  3. ajouter main.jsde electron-quick-start(nous le renommerons electron-starter.js, pour plus de clarté)
  4. modifier l'appel à mainWindow.loadURL(in electron-starter.js) pour utiliser localhost:3000(webpack-dev-server)
  5. ajouter une entrée principale à package.jsonpourelectron-starter.js
  6. ajouter une cible d'exécution pour démarrer Electron package.json
  7. npm start suivi par npm run electron

Les étapes 1 et 2 sont assez simples. Voici le code des étapes 3 et 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.

(Essentiel)

Et pour les étapes 5 et 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 ." } }

(Essentiel)

Lorsque vous exécutez les commandes npm à l'étape 7, vous devriez voir ceci:

Vous pouvez apporter des modifications en direct au code React et vous devriez les voir reflétées dans l'application Electron en cours d'exécution.

Cela fonctionne bien pour le développement, mais présente deux inconvénients:

  • la production n'utilisera pas webpack-dev-server. Il doit utiliser le fichier statique de la construction du projet React
  • (petite) nuisance pour exécuter les deux commandes npm

Spécification de loadURL en production et en développement

En développement, une variable d'environnement peut spécifier l'url de mainWindow.loadURL(in electron-starter.js). Si la variable d'environnement existe, nous l'utiliserons; sinon, nous utiliserons le fichier HTML statique de production.

Nous ajouterons une cible d'exécution npm (à package.json) comme suit:

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

Mise à jour: les utilisateurs de Windows devront faire ce qui suit: (merci à @bfarmilo)

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

Dans electron-starter.js, nous modifierons l' mainWindow.loadURLappel comme suit:

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

(Essentiel)

Il y a un problème avec ceci: create-react-app(par défaut) construit un index.htmlqui utilise des chemins absolus. Cela échouera lors du chargement dans Electron. Heureusement, il existe une option de configuration pour changer cela: définissez une homepagepropriété dans package.json. (La documentation Facebook sur la propriété est ici.)

Nous pouvons donc définir cette propriété sur le répertoire actuel et npm run buildl'utiliser comme chemin relatif.

"homepage": "./",

Utilisation de Foreman pour gérer les processus React et Electron

Pour plus de commodité, je préfère ne pas

  1. lancer / gérer à la fois le serveur de développement React et les processus Electron (je préfère m'en occuper)
  2. attendez que le serveur de développement React démarre, puis lancez Electron

Foremen est un bon outil de gestion de processus. On peut l'ajouter,

npm install --save-dev foreman

et ajoutez ce qui suit Procfile

react: npm startelectron: npm run electron

(Essentiel)

Cela concerne (1). Pour (2), nous pouvons ajouter un simple script de nœud ( electron-wait-react.js) qui attend que le serveur de développement React démarre, puis démarre 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); });

(Essentiel)

REMARQUE: Foreman décale le numéro de port de 100 pour les processus de différents types. (Voir ici.) Donc, electron-wait-react.jssoustrait 100 pour définir correctement le numéro de port du serveur de développement React.

Modifiez maintenant le Procfile

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

(Essentiel)

Enfin, nous allons modifier les cibles d'exécution package.jsonpour les remplacer electron-devpar:

"dev" : "nf start"

Et maintenant, nous pouvons exécuter:

npm run dev
MISE À JOUR (25/01/17): J'ai ajouté la section suivante en réponse à certains commentaires d'utilisateurs (ici et ici). Ils doivent accéder à Electron à partir de l'application react et une simple demande ou importation génère une erreur. Je note une solution ci-dessous.

Accéder à Electron depuis l'application React

Une application Electron a deux processus principaux: l'hôte / wrapper Electron et votre application. Dans certains cas, vous souhaitez accéder à Electron depuis votre application. Par exemple, vous souhaiterez peut-être accéder au système de fichiers local ou utiliser Electron ipcRenderer. Mais si vous faites ce qui suit, vous obtiendrez une erreur

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

Il y a une discussion sur cette erreur dans divers problèmes de GitHub et de débordement de pile, tels que celui-ci. La plupart des solutions proposent des modifications de configuration du pack Web, mais cela nécessiterait l'éjection de l'application.

Cependant, il existe une solution de contournement / hack simple.

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

Emballer

Pour plus de commodité, voici un dépôt GitHub qui contient toutes les modifications ci-dessus, avec des balises pour chaque étape. Mais là, il n'y a pas beaucoup de travail pour démarrer une application Electron qui utilise create-react-app. (Ce message est beaucoup plus long que le code et les modifications dont vous auriez besoin pour intégrer les deux.)

Et si vous utilisez create-react-app, vous voudrez peut-être consulter mon message, les tests de débogage dans WebStorm et create-react-app.

Merci d'avoir lu. Vous pouvez consulter plus de mes messages sur justideas.io

MISE À JOUR (2/2/17). Un lecteur, Carl Vitullo, a suggéré d'utiliser à la npm startplace de npm run devet a soumis une demande d'extraction avec les modifications, sur GitHub. Ces modifications sont disponibles dans cette branche.