Comment améliorer l'expérience de développement frontend sans bundler

Découvrez comment utiliser les modules ES6 et les opérateurs de service pour lancer une application Web sans Webpack ou Rollup.

Exemple typique d'un développeur attendant que le paquet soit prêt.

introduction

Aussi disponible en italien

La spécification ECMAScript 2015 (ES6) a introduit JavaScript et le développement Web dans une nouvelle ère, composée de syntaxes claires, d’un meilleur échafaudage des fichiers source et d’un ensemble d’outils de développement disponibles dans d’autres contextes de programmation, comme l’analyse de code statique, les systèmes de dépendances, l’auto-complétion, etc. .

Tout cela a eu un prix: le lancement d’une application Web dans le navigateur peut désormais nécessiter des centaines de modules de nœuds, un observateur pour détecter les modifications de fichiers et des reconstructions de sources épuisantes. Quelques secondes perdues peuvent facilement devenir des heures par semaine, un gaspillage d’espace disque, une consommation extrême de RAM et de CPU et, pour les ordinateurs lents, des ventilateurs tourbillonnant comme des moteurs aéronautiques.

Étude de cas

En tant que développeur, je souhaite créer une application classique de liste de tâches, à l'aide d'une bibliothèque de composants avec support JSX, et l'exécuter dans le navigateur, sans avoir à démarrer un bundle, un transpiler et un observateur.

Disons que nous aimons tous Preact (comme il se doit): obtenir uniquement le passe-partout (et ses dépendances) signifie télécharger environ 1 500 modules de nœud, environ 26 000 fichiers et nécessite un espace disque total d’environ 200 Mo. La plupart de ces dépendances sont utilisées pour exécuter un serveur Web cuit par Webpack.

Mais nous ne voulons rien de tout cela, nous devons donc nous fier uniquement aux fonctionnalités d’un navigateur moderne.

Comment charger une application ES6 / 7 / X dans le navigateur sans bundler

Depuis Chrome 62, Edge 16, Safari 11 et Firefox 54, il est possible d'importer un module ES6 dans le navigateur:

Lorsque l'analyseur HTML rencontre cette balise, il extrait le fichier source spécifié et résout de manière récursive toutes les instructions d'importation et d'exportation.

Bien que cette nouvelle fonctionnalité soit merveilleuse, il ne suffit pas de lancer une application Web moderne et complexe. Les navigateurs ne peuvent résoudre que les dépendances relatives et ils ne disposent d'aucune information sur les dépendances NPM. De plus, l'utilisation de JSX génèrera plusieurs erreurs de syntaxe, car il ne s'agit pas d'un standard du langage JavaScript.

Les travailleurs de service à la rescousse

Quand tout semble perdu, les travailleurs des services viennent à notre aide. Une fois enregistrés, ces travailleurs spéciaux peuvent intercepter les demandes du réseau et gérer leur réponse.

Nous pouvons donc utiliser SW pour:

  • intercepter les requêtes JavaScript;
  • récupérer des fichiers;
  • recherchez les instructions non relativesimport et remappez-les dans le dossier node_modules;
  • détecter la syntaxe JSX et transpiler toutes les expressions JSX en JavaScript;
  • renvoyer les fichiers modifiés au thread principal.

À ce stade, le navigateur doit pouvoir gérer les fichiers JavaScript et résoudre leurs dépendances.

Informations complémentaires sur les opérateurs de service sur MDN: Utilisation des opérateurs de service

Développer Unchained

Unchained est une preuve de concept hébergée sur GitHub qui met cette idée en pratique. Comme les bundles et transpilers que nous utilisons déjà, il a été conçu pour scinder le processus en étapes et il est enfichable.

Il fournit des aides utiles pour l’enregistrement des travailleurs de service, un polyfill pour prendre en charge l’importation dynamique et son noyau (similaire à l’API cumulative) peut résoudre les importations de fichiers JavaScript en procédant comme suit:

  • Transformation: modifier le code source et la transpilation de syntaxes non standard;
  • Résolution: recherchez et résolvez des instructions import;
  • Finalisation: renvoie le code généré.

Toutes les mises à jour du dernier lot sont effectuées avec Babel Standalone, une distribution de Babel qui s'exécute dans le navigateur et permet aux plug-ins de gérer directement l'arborescence de syntaxe abstraite (AST) du fichier, de suivre les modifications et de générer le fond de carte final.

Les plugins Unchained suivants sont déjà disponibles (et assez basiques pour le moment):

  • common: remplace les expressions CommonJS, telles que require andmodule.exports, par des instructions ES6;
  • babel: communique avec Babel Standalone et ses plugins afin de transpiler des syntaxes non standard telles que JSX, Flow ou Typescript (!);
  • text: convertit les fichiers texte en modules ES6;
  • json: convertit les fichiers JSON en modules ES6;
  • env: effectue l'injection de variables d'environnement (transmises à l'aide d'un objet JavaScript, car les navigateurs n'ont pas d'accès direct aux variables env réelles);
  • résoudre: est une implémentation partielle de l'algorithme de résolution de nœud.

De plus, Unchained utilise l'interface de cache du navigateur pour ignorer les fichiers déjà résolus. À l'aide de l'en-tête ETag, il peut également détecter les modifications. Les recharges de pages et les injections de code sont donc extrêmement rapides.

Rendez le développeur heureux

Pour revenir à notre cas, nous pouvons penser à une solution et, pour la mettre en œuvre, nous utilisons l'exemple simple présenté sur la page d'accueil de preactjs.com.

Configuration du projet

Au début, nous n'avons besoin que de deux dépendances:

npm init -y
npm installez préactiver unchained-js
N ° 2 modules, 65 éléments, <2Mo

et les fichiers suivants:

  • index.html: où le service worker est enregistré et les fichiers d'application importés
  • sw.js: le service worker à enregistrer, il utilise la bibliothèque Unchained pour transpiler le code source
  • index.js: le fichier JavaScript principal
  • todolist.component.js: définition de la classe de composant TodoList.

Maintenant, lancez simplement un serveur local (ici installé globalement):

npm install -g serveur http
serveur http.

et fait!

Dans la console, Unchained enregistre tous les fichiers importés au lancement: ultérieurement, il utilisera le cache pour les rechargements suivants.

À chaque changement de fichier, par exemple Si nous modifions l’étiquette du bouton dans todolist.component.js et l’enregistrons, seul le fichier en question sera rechargé par le navigateur.

Un projet Git de cet exemple est disponible ici.

Conclusions

Bien que le support du navigateur soit limité à Chrome et Firefox (pour celui-ci, il n'est pas disponible par défaut, mais vous devez activer le drapeau dom.moduleScripts.enabled), je pense que cette approche peut simplifier la structure du projet et sa configuration:

  • cela réduit considérablement le nombre de dépendances à installer;
  • transpiler ne bloque pas le fil principal de l’interface utilisateur, car tout se passe dans le contexte de l’agent de service;
  • après le premier lancement, les mises à jour du code source sont très rapides;
  • le fractionnement de code avec import dynamique () ne fonctionne que;
  • soutien sourcemaps.

Mais il y a aussi des inconvénients:

  • le premier lancement n'est pas performant (en raison de l'absence d'accès direct au système de fichiers et de la nécessité de créer un AST pour chaque fichier);
  • TreeShaking est manquant, une fonctionnalité disponible dans Rollup et Webpack 4.

Test réel

Les choses sont toujours faciles dans une application To-Do List, mais qu'en est-il des exemples concrets? J'ai remplacé Rollup par Unchained dans un projet complexe sur lequel je travaille, qui comporte de nombreuses dépendances telles que jQuery, Moment, CKEditor et une bibliothèque de composants, avec les résultats suivants:

Comparaison faite avec un MacBook Pro (Retina, 15 pouces, mi-2015) et Chrome 63.0.3239.84

Futur

Bien que Unchained couvre tous les aspects de cette étude simple, j'aimerais approfondir certains sujets et améliorer les résultats:

  • accélérer la première exécution en utilisant uniquement les transformations AST (désactivée car un problème est survenu lors de la génération des cartes source);
  • vérifier s'il est possible d'intégrer Turbo une fois disponible, afin d'améliorer le processus de résolution des chemins non relatifs;
  • étendre la compatibilité aux navigateurs qui ne prennent pas en charge les services Workers ou les modules ES6;
  • étendre la prise en charge à l'environnement de nœud pour obtenir des ensembles d'applications compatibles à 100% avec l'aperçu intégré au navigateur;
  • blob (pour le chargement des images et des polices) et plugins postCSS.
Remerciement spécial
Un grand merci à xho et à SteRosanelli pour leur patience dans l'examen de cet article.