Développement

Utiliser ExpressJS avec Electron (Partie 1)

Prérequis : Avoir Node.js version 8 ou supérieur correctement installé sur votre machine.

Dans le développement informatique d’aujourd’hui, le Node.js est de plus en plus répandu, avec de plus en plus de bibliothèques. L’une d’elle est notamment connue grâce aux nombreuses applications créées avec : ElectronJS.

Quelques exemples d’applications utilisant Electron : Discord, Visual Studio Code ou encore Gitkraken.

Passons maintenant aux choses sérieuses. Ce tutoriel sera décomposé en 2 parties, la première pour la génération d’une première application, et la seconde pour la génération de paquets prêt à distribuer.

Installation

Pour commencer à développer notre application, il faut tout d’abord « générer » le projet. Ouvrez une console de commande, placez-vous dans un répertoire vide et entrez la commande suivante :

npm init

Suivez les instructions décrites dans le terminal. Pour les moins patient, appuyez sur la touche « Entrée » suffisamment jusqu’à ce que le terminal vous laisse à nouveau la main. Une fois la génération du fichier package.json effectuée, ouvrez votre éditeur favoris et ouvrez-le, il devrait ressembler à ceci :

{
  "name": "electronexpress",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Pour le moment, changez simplement la valeur de « main » pour « main.js ».

Nous allons maintenant installer ElectronJS et ExpressJS. Pour cela, tapez les commandes suivantes :

npm install -D electron@latest
npm install express --save
npm install ejs --save

A présent, les deux outils sont installés et prêts à être utilisés.

Création des environnements

Pour cet exemple, nous aurons deux environnements exécutables : le premier avec ElectronJS et l’autre seulement avec du NodeJS classique.

Fichiers généraux

Les premiers fichiers que nous allons créer servirons pour les deux environnements. Sans ces deux fichiers là, l’application ne va tout simplement pas démarrer.

Fichier router.js :

/* Appel de tous nos outils */
const express = require('express');
const expressApp = express();
const http = require('http').Server(expressApp);

const path = require('path');

/* Initialisation des variables */
const router = {
    isStarted: false
};

function start(callback) {
    if (router.isStarted === false) {
        init(function () {
            loadRoutes(function () {
                /* Lance le serveur web sur le port 3000 */
                http.listen(3000, function () {
                    console.log('Application is running on port 3000');
                    router.isStarted = true;
                    if (typeof callback != 'undefined') {
                        callback();
                    }
                });
            });
        });
    } else {
        console.log("Application already started");
        if (typeof callback != 'undefined') {
            callback();
        }
    }
}

function init(callback) {
    /* On s'assure que le serveur n'est vraiment pas démarré */
    router.isStarted = false;

    /* J'utilise ici EJS comme moteur de template */
    expressApp.set('view engine', 'ejs');

    /* assets sera le répertoire où se trouverons nos fichiers côté client */
    expressApp.use(express.static(path.join(__dirname, 'assets')));
    
    /* views est défini comme notre dossier de vues par défaut */
    expressApp.set('views', path.join(__dirname, '/views/'));

    if (typeof callback != 'undefined') {
        callback();
    }
}

/* ROUTES */

function loadRoutes(callback) {
    expressApp.get('/', function (req, res) {
        res.render('homepage/index');
    });

    if (typeof callback != 'undefined') {
        callback();
    }
}

module.exports = {
    start: start
};

Fichier app.js

var app = {};

function start(callback) {
    init(function() {
        /* On démarre le routeur défini juste avant */
        app.router.start(function() {
            if(typeof callback != 'undefined') {
                callback();
            }
        });
    });
}

function init(callback) {
    /* On instancie notre module router */
    app.router = require('./router');

    if(typeof callback != 'undefined') {
        callback();
    }
}

module.exports = {
    start: start
};

Notre fichier app.js nous servira de point d’entrée pour nos deux futurs scripts.

Démarrage sans Electron.js

Dans un premier temps, nous allons créer un fichier permettant de lancer notre application sans utiliser Electron.js : le fichier main-without-electron.js

const app = require('./app');

/* Démarre uniquement l'application Express */
app.start();

Pour tester votre fichier, retournez dans votre console et lancer la commande :

node ./main-without-electron.js

Si votre console vous renvoie « Application is running on port 3000 », tout est bon.

Démarrage avec Electron.js

Notre application est, à la base, concentrée sur Electron.js et c’est pour cela que notre projet ne contient pas encore de fichier nommé main.js. Il est donc maintenant temps de le créer :

const electron = require('electron');
const app = require('./app');

let window;

function createWindow() {
    /* Créer une fenêtre de 800px par 600px sans bordures */
    window = new electron.BrowserWindow({
        width: 800,
        height: 600,
        frame: false
    });

    /* Si vous décommentez cette ligne, vous verrez la console de débug Chrome */
    /* window.webContents.openDevTools(); */

    /* Display the homepage of the server */
    window.loadURL('http://127.0.0.1:3000');

    /* Lorsque la fenêtre est fermée, on l'indique au système */
    window.on('closed', () => {
        window = null;
    });
}

/* On attend qu'Electron.js soit prêt pour créer la fenêtre */
electron.app.on('ready', function () {
    app.start(function () {
        createWindow();
    });
});

/* Cette fonction arrête totalement l'application 
   lorsque toutes les fenêtres sont fermées */
electron.app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electron.app.quit();
    }
});

/* Fonction utile pour MacOS */
electron.app.on('activate', () => {
    if (win === null) {
        createWindow();
    }
});

Pour pouvoir tester notre application Electron.js, il va falloir modifier légèrement notre fichier package.json :

{
  // ...
  "scripts": {
    "start-electron": "electron .",
    "start": "node ./main-without-electron.js",
  },
  // ...
}

Ces deux commandes permettent un raccourci pour lancer les deux fichiers précédents :

npm run start-electron // Lance le projet avec Electron.js
npm run start // Lance le projet sans Electron.js

Transformation côté client

Tout ce qu’on a fait jusqu’à présent ne concerne que le « coeur » de l’application, cependant il nous manque la forme pour que le sujet soit complet ! Pour cela, nous avons besoin de créer 2 dossiers à la racine du projets : assets et views.

Je vous conseille de garder un système propre et organiser. Dans le dossier assets, j’ai par exemple un dossier de CSS contenant tous mes fichiers de styles, et dans le dossier JS, les fichiers contenant le JavaScript à exécuter côté client.

Dans le dossier views, mes vues sont organisées de manière très classique et pour l’instant, ne contient qu’un seul fichier : homepage/index.ejs (déclaré dans le fichier router.js). Ne mettez rien dans ce fichier pour le moment, vous allez pouvoir économiser quelques lignes.

Infrastructure de l'application
Infrastructure de l’application

Ajout d’un layout par défaut

Pour nous simplifier la tâche, nous allons pouvoir appliquer un layout à nos vue, une sorte de calque à appliquer de partout dans nos fichier EJS. Pour cela, il vous faut un nouveau paquet NPM :

npm install --save express-ejs-layouts

Retournez dans votre fichier router.js et modifiez le comme ceci :

/* Appel de tous nos outils */
const express = require('express');
const expressApp = express();
const http = require('http').Server(expressApp);

const path = require('path');

/* Ajout de express-ejs-layouts */
const ejsLayout = require('express-ejs-layouts');

/* Initialisation des variables */
const router = {
    isStarted: false
};

function start(callback) {
    if (router.isStarted === false) {
        init(function () {
            loadRoutes(function () {
                /* Lance le serveur web sur le port 3000 */
                http.listen(3000, function () {
                    console.log('Application is running on port 3000');
                    router.isStarted = true;
                    if (typeof callback != 'undefined') {
                        callback();
                    }
                });
            });
        });
    } else {
        console.log("Application already started");
        if (typeof callback != 'undefined') {
            callback();
        }
    }
}

function init(callback) {
    /* On s'assure que le serveur n'est vraiment pas démarré */
    router.isStarted = false;

    /* Ajout de express-ejs-layouts */
    expressApp.use(ejsLayout);

    /* J'utilise ici EJS comme moteur de template */
    expressApp.set('view engine', 'ejs');

    /* assets sera le répertoire où se trouverons nos fichiers côté client */
    expressApp.use(express.static(path.join(__dirname, 'assets')));

    /* views est défini comme notre dossier de vues par défaut */
    expressApp.set('views', path.join(__dirname, '/views/'));

    if (typeof callback != 'undefined') {
        callback();
    }
}

/* ROUTES */

function loadRoutes(callback) {
    expressApp.get('/', function (req, res) {
        res.render('homepage/index');
    });

    if (typeof callback != 'undefined') {
        callback();
    }
}

module.exports = {
    start: start
};

Dans le dossier views, créez un nouveau fichier nommé « layout.ejs » et insérez y quelque chose de similaire à cet exemple :

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <link rel="stylesheet" href="css/bootstrap.min.css" />
</head>

<body>
    <nav class="navbar navbar-dark bg-light">
        <a class="navbar-brand">Navbar</a>
        <button class="btn btn-outline-danger" id="quit">Quitter</button>
    </nav>
    <div class="container">
        <%- body %>
    </div>
</body>
</html>

Vous avez à présent votre premier layout, et vous pouvez remplir le fichier homepage/index.ejs avec le corps de votre page. Exemple :

<h1>Hello World!</h1>
<p>Insidias principem infudisset graves ignotus Constantium Constantium siquid hoc graves postea Constantium apud
  titulo et cautela graves siquid cautela modi hoc Constantium auribus graves in postea narrabimus huius inplacabilem
  hoc haeserat plagas eius siquid postea et sed consarcinantibus haeserat acerbum plagas insidias eius infudisset et
  narrabimus auribus quivis auribus eius cautela in titulo inplacabilem eius acerbum narrabimus Constantium dissimilem
  narrabimus in causarum quivis plagas modi in Constantium insidias quivis modi postea quivis et ignotus dissimilem
  ignotus auribus peiores ignotus ignotus in acerbum eius et huius peiores medium modi peiores quivis acerbum peiores
  in eius sed Constantium modi causarum medium causarum.</p>
<button class="btn btn-success" id="test">Notification</button>
<button class="btn btn-danger" id="quit">Quit</button>

Nous ne pouvons malheureusement pas poster les balises et fichiers JavaScript client ici, c’est pour celà que vous pouvez télécharger la version complète du projet actuel en cliquant ici. N’oubliez pas de lancer un npm install dans le dossier extrait pour récupérer toutes les dépendances NPM !

Pour vérifier que tout fonctionne, lancer la commande :

npm run start-electron

Et vérifier le résultat. En se basant sur les exemples que je vous ai donné plus tôt, le résultat devrait ressembler à ça :

Exemple ElectronJS & ExpressJS
Exemple ElectronJS & ExpressJS

Vous avez à présent quelques bases pour vous servir d’ElectronJS et de ExpressJS, vous êtes à présent libres de créer ce que vous souhaitez !

Retrouvez la partie 2 juste ici !

1 Comment
  • Jericho1060
    3:41 , 2 avril 2019

    Bonjour,
    Super guide, j’ai eu cependant un soucis quand je rajoute des routes, elles s’ouvrent toutes dans une nouvelle fenêtre au lieu de la fenêtre actuelle, exception faite de la route ‘/’. J’ai peut être raté quelque chose.
    Pour corriger le soucis, j’ai ajouté dans le main.js, a la fin de la fonction createWindow, une petite modification pour l’événement qui déclenche l’ouverture de la nouvelle fenêtre:

    window.webContents.on('new-window', (event, url) => {
    event.preventDefault();
    window.loadURL(url);
    });

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *