Installer une application TypeScript sur un YoctoHub

Installer une application TypeScript sur un YoctoHub

Le langage TypeScript est une bonne amélioration par rapport à JavaScript, puisqu'il permet de détecter des erreurs à la compilation plutôt qu'à l'exécution. Mais s'il est relativement aisé à intégrer avec Node.js, son utilisation pour écrire des interfaces HTML sans serveur Node.js n'est pas aussi triviale. Nous allons donc vous montrer comment implémenter une application HTML en TypeScript en optimisant les transferts réseau, de sorte à pouvoir l'installer directement sur un YoctoHub-Ethernet.


Comme exemple concret, nous vous proposons de reprendre en TypeScript et avec les technologies de ce jour l'exemple d'interface web pour piloter un volet roulant que nous avions réalisé en 2019.

Structure du projet

Contrairement à l'exemple d'origine où le code JavaScript pouvait être inséré directement dans la page HTML, il n'est pas possible de mettre directement du code TypeScript dans un fichier HTML: les navigateurs ne savent pas le compiler. Il existe bien des astuces pour charger un compilateur TypeScript dans page HTML, mais cela augmente beaucoup la quantité de code à transférer, donc c'est sans intérêt pour nous. Une application HTML en TypeScript aura donc en général la structure minimale suivante:

  • une ou plusieurs pages HTML
  • un ou plusieurs fichiers TypeScript (.ts)
  • un fichier tsconfig.json, avec les options de compilation TypeScript

La page HTML ne référencera pas directement les fichiers sources en TypeScript, mais la version compilée en JavaScript (.js). Et contrairement à ce qui se faisait avant l'apparition des systèmes de modules, la page ne doit plus inclure explicitement tous les fichiers JavaScript, mais uniquement le point d'entrée. Les autres modules seront chargés automatiquement comme des dépendances, en fonction des ordres import qu'ils contiennent. Voici ainsi à quoi ressemble le nouveau fichier HTML de notre application:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Roller Shutter Control</title>
    <!-- Chargement du code TypeScript compilé -->
    <script type="module" src="app.js"></script>
</head>
<body>
<div id="mainUI" style="position: absolute; left:50%; top: 50%; text-align: center; font-family: sans-serif; width:30ex;">
    <span id="UP"></span><br>
    Roller shutter control<br>
    <span id="DOWN"></span>
</div>
</body>
</html>


Notez l'inclusion du script JavaScript avec le tag type="module". Il change fondamentalement la manière dont le code JavaScript inclus sera traité. Contrairement à un fichier JavaScript inclus de la manière traditionnelle, un module JavaScript ne définit pas de symboles globaux, pour respecter l'encapsulation entre les modules et pour éviter les conflits de noms. Chaque module peut par contre importer des dépendances, et ainsi accéder aux symboles qu'il a explicitement importé. Les dépendances sont automatiquement chargées dynamiquement par le navigateur, ce qui n'était pas possible dans un fichier JavaScript traditionnel.

Attention, pour que ce mécanisme de chargement de modules fonctionne, il faut impérativement compiler le code TypeScript pour qu'il produise un module ECMAScript 2015, avec l'option "module": "es2015" dans le fichier tsconfig.json.

Pour déployer cette application sur un YoctoHub-Ethernet, il faut

  1. installer TypeScript si ce n'est pas déjà fait, comme décrit dans cet article;
  2. compiler les fichiers TypeScript en lançant la commande tsc;
  3. copier uniquement le(s) fichier(s) HTML et le(s) fichier(s) JavaScript.

Voici comment se déroule le chargement de notre application de contrôle de volet roulant en l'état:

Chargement des modules ECMAScript 2015 par le navigateur
Chargement des modules ECMAScript 2015 par le navigateur



Empaquetage

En observant les transferts lors du chargement de l'application, on se rend compte que le transfert des classes JavaScript de l'API Yoctopuce prend la plus grosse partie du temps, et qu'il serait certainement possible de réduire le temps de chargement en les compressant.

Pour compresser du code JavaScript, on utilise deux mécanismes complémentaires:

  1. la minification, qui consiste à supprimer les espaces et raccourcir le nom des symboles locaux
  2. la compression proprement dite, qui consiste à transférer le code sous forme d'un flux compressé binaire plutôt que d'utiliser le format textuel, beaucoup plus gros

Or, la compression binaire fonctionne d'autant mieux que le fichier traité est grand. Il est donc souhaitable de rajouter une étape intermédiaire qui rassemble tous les fichiers JavaScript nécessaires au projet en un seul gros fichier, qu'on appelle bundle. Le fait de n'avoir plus qu'un fichier JavaScript facilite aussi l'installation. Mais malheureusement on ne peut pas simplement mettre bout-à-bout des modules ECMAScript comme on l'aurait fait avec du code JavaScript traditionnel: il faut effectuer une transformation, qui correspond à la résolution des imports/exports entre les modules.

Il existe une quantité d'outils capables de gérer cette résolution des imports, et qui gèrent en même temps la minification: webpack, rollup.js, parcel... Ces outils existaient avant l'avènement des modules ECMAScript et étaient destinés à d'autres types de modules pour JavaScript. Mais du coup, ils ont tendent à compliquer les choses, en rajoutant du code au projet pour résoudre dynamiquement les dépendance, alors qu'on voudrait au contraire les simplifier. Ce qu'on aimerait, c'est simplement un linker pour modules ECMAScript, comme cela existe pour les langages compilés traditionnels.

Par chance, nous ne somme pas les premiers à rechercher cela: nous avons finalement trouvé un petit outil simple et efficace nommé esbuild qui fait exactement ce que nous voulons. Et contrairement à WebPack qui traine avec lui une centaine de dépendances, esbuild n'a besoin de rien d'autre que lui-même pour fonctionner, et il est très rapide.

Pour créer notre bundle minifié, il nous suffit donc d'installer esbuild et de lancer la commande suivante dans le même répertoire:

esbuild app.js --bundle --sourcemap --minify --outfile=app.min.js


L'argument --sourcemap permet de créer un fichier app.min.js.map qui, s'il est présent sur le serveur, permet d'effectuer le deboggage dans le fichier source, plutôt que dans la version minifiée illisible.

Notez que nous avons demandé à esbuild de créer le bundle à partir du fichier app.js, et non à partir du fichier TypeScript app.ts. On pourrait être tenté de le faire puisque esbuild supporte nativement le TypeScript, mais malheureusement esbuild ne supporte par les types const enum de TypeScript. Du coup, le code compilé n'est pas compatible avec les librairies TypeScript compilées avec le compilateur officiel, comme la librairie Yoctopuce. C'est le seul défaut que nous avons rencontré avec cet outil, mais il est facilement contournable en lançant simplement tsc avant de créer le bundle.

Compression

Si votre application est prévue pour fonctionner depuis un serveur Web traditionnel comme Apache, il suffit de placer le fichier app.min.js et le fichier HTML (référençant app.min.js à la place de app.js) sur le serveur. Le transfert du fichier JS se fera automatiquement en mode compressé si le serveur est configuré correctement, par exemple avec mod_deflate.

Malheureusement les YoctoHubs n'ont pas la puissance de calcul permettant une compression au vol. Si vous voulez que votre application soit transmise compressée par un YoctoHub-Ethernet ou un YoctoHub-Wireless-n, il vous faut pré-compresser le bundle au format GZIP (extension .gz) avant de le charger sur le YoctoHub.

Sous Linux, il suffit de la ligne de commande suivante:

gzip -9 app.min.js


et vous aurez un fichier app.min.js.gz de la taille minimale.

Sous Windows, installez l'utilitaire gratuit 7-Zip et cliquez avec le bouton droit sur le fichier app.min.js. Dans le sous-menu 7-Zip choisissez l'option Add to archive... pour ouvrir 7-Zip. Sélectionnez le format gzip, le niveau de compression Ultra et pressez OK.

Il ne vous reste plus qu'à poser le fichier HTML et le fichier app.min.js.gz sur le YoctoHub, et votre application est prête à fonctionner. Au moment où le navigateur essaiera de charger le fichier app.min.js, le YoctoHub se rendra compte qu'il n'est pas présent tel quel mais en format GZIP, et l'enverra avec un Content-Encoding correspondant pour que ce soit transparent pour le navigateur. Attention, cette fonctionnalité a été rajoutée récemment, donc prenez soin de mettre à jour le firmware de votre YoctoHub avant d'essayer.

Résultat

Voici la trace du chargement de la même application, cette fois bundlée par esbuild et compressée:

Chargement du bundle compressé par le navigateur
Chargement du bundle compressé par le navigateur


Comme on s'en doutait, la compression du code JavaScript vaut largement la peine... on gagne plus qu'un facteur 10 au temps de chargement!

Vous trouverez tous les fichiers de cet exemple dans la librairie TypeScript de Yoctopuce, dans le sous-répertoire example_html/Prog-Bundle. Nous y avons ajouté un fichier package.json qui permet d'installer localement esbuild comme une dépendance de développement. Vous pouvez donc utiliser les commandes suivantes:
npm install pour installer les dépendances (esbuild et la librairie Yoctopuce)
npm run build pour compiler et construire le bundle

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.