Il y a deux ans, j'ai écrit sur une
technique qui est maintenant communément appelée le modèle module / nomodule. Son application vous permet d'écrire du code JavaScript en utilisant les capacités d'ES2015 +, puis d'utiliser des bundlers et des transpilers pour créer deux versions de la base de code. L'un d'eux contient une syntaxe moderne (il est chargé à l'aide d'une structure comme
<script type="module">
, et le second est la syntaxe ES5 (il est chargé à l'aide de
<script nomodule>
). Le modèle module / nomodule permet d'envoyer aux navigateurs qui prennent en charge les modules, beaucoup moins de code que les navigateurs qui ne prennent pas en charge cette fonctionnalité, ce modèle est désormais pris en charge par la plupart des frameworks Web et des outils de ligne de commande.

Auparavant, même en considérant la possibilité d'envoyer du code JavaScript moderne en production, et même si la plupart des navigateurs prenaient en charge les modules, je recommandais de collecter le code en bundles.
Pourquoi? Surtout parce que j'avais l'impression que le chargement des modules dans le navigateur était lent. Même si les protocoles récents, tels que HTTP / 2, prenaient théoriquement en charge le chargement efficace de plusieurs fichiers, toutes les études de performances de l'époque ont conclu que l'utilisation de
bundlers est toujours plus efficace que l'utilisation de modules.
Mais il faut admettre que ces études étaient incomplètes. Les cas de test utilisant les modules qui y étaient étudiés consistaient en des fichiers de code source non optimisés et non minimisés qui étaient déployés en production. Il n'y avait aucune comparaison du bundle optimisé avec des modules avec le script classique optimisé.
Cependant, pour être honnête, il n'existait aucun moyen optimal de déployer les modules à ce moment-là. Mais maintenant, grâce à certaines améliorations modernes des technologies de bundler, il est possible de déployer du code de production sous la forme de modules ES2015 en utilisant à la fois des commandes d'importation statiques et dynamiques, et en même temps de recevoir des performances supérieures à celles qui peuvent être obtenues en utilisant les options disponibles, dans lesquelles les modules ne sont pas utilisés.
Il est à noter que sur le
site sur lequel le matériel original est publié, dont la première partie de la traduction est publiée aujourd'hui, les modules sont utilisés en production depuis plusieurs mois.
Idées fausses sur les modules
Beaucoup de gens avec qui j'ai parlé rejettent complètement les modules, ne les considérant même pas comme l'une des options pour les applications de production à grande échelle. Beaucoup d'entre eux citent l'
étude même que j'ai déjà mentionnée. À savoir, cette partie de celui-ci, qui stipule que les modules ne doivent pas être utilisés en production, sauf s'il s'agit de «petites applications Web qui comprennent moins de 100 modules qui diffèrent dans un arbre de dépendance relativement« petit »(c'est-à-dire - un dont la profondeur ne dépasse pas 5 niveaux). »
Si vous avez déjà consulté le répertoire
node_modules
de l'un de vos projets, vous savez probablement que même une petite application peut facilement avoir plus de 100 modules de dépendance. Je veux vous offrir un aperçu du nombre de modules disponibles dans certains des packages npm les plus populaires.
C'est là que la principale idée fausse concernant les modules est enracinée. Les programmeurs pensent que lorsqu'il s'agit d'utiliser des modules en production, ils n'ont que deux options. La première consiste à déployer tout le code source dans sa forme existante (y compris le répertoire
node_modules
). La seconde est de ne pas utiliser du tout de modules.
Cependant, si vous regardez attentivement les recommandations de l'étude citée ci-dessus, vous constaterez qu'il n'y a rien à dire que le chargement des modules est plus lent que le chargement des scripts réguliers. Il ne dit pas que les modules ne devraient pas être utilisés du tout. Il évoque simplement le fait que si quelqu'un déploie des centaines de fichiers de module non infectés en production, Chrome ne pourra pas les charger aussi rapidement qu'un seul ensemble réduit. En conséquence, l'étude conseille de continuer à utiliser des bundlers, des compilateurs et des minificateurs.
Mais tu sais quoi? Le fait est que vous pouvez utiliser tout cela et utiliser des modules en production.
En fait, les modules sont un format dans lequel nous devons nous efforcer de convertir le code, car les navigateurs savent déjà comment charger les modules (et les navigateurs qui ne peuvent pas le faire peuvent charger une solution de secours en utilisant le mécanisme du nomodule). Si vous regardez le code généré par les bundlers les plus populaires, vous trouverez de nombreux fragments de modèle dont le but est uniquement de charger dynamiquement d'autres codes et de gérer les dépendances. Mais tout cela ne sera pas nécessaire si nous utilisons simplement les modules et les expressions d'
import
et d'
export
.
Heureusement, au moins l'un des bundlers modernes populaires (
Rollup ) prend en charge les modules sous forme de
données de
sortie . Cela signifie que vous pouvez traiter le code avec un bundler et déployer des modules dans la production (sans utiliser de fragments de modèle pour charger le code). Et, comme Rollup a une excellente implémentation de l'algorithme de tremblement d'arbre (le meilleur que j'ai vu dans les bundlers), la création de programmes sous forme de modules à l'aide de Rollup vous permet d'obtenir un code plus petit que la taille du même code obtenu lors de l'application. d'autres mécanismes disponibles aujourd'hui.
Il convient de noter qu'ils
prévoient d' ajouter la prise en charge des modules dans la prochaine version de Parcel. Webpack ne prend pas encore en charge les modules en tant que format de sortie, mais le
voici - des discussions qui se concentrent sur ce problème.
Une autre idée fausse concernant les modules est que certaines personnes pensent que les modules ne peuvent être utilisés que si 100% des dépendances du projet utilisent des modules. Malheureusement (je pense que c'est un grand regret), la plupart des packages npm sont toujours en cours de préparation pour publication au format CommonJS (certains modules, même ceux écrits à l'aide des fonctionnalités ES2015, sont traduits au format CommonJS avant d'être publiés au npm)!
Ici, encore une fois, je tiens à noter que Rollup a un plugin (
rollup-plugin-commonjs ) qui prend le code source d'entrée écrit à l'aide de CommonJS et le convertit en code ES2015. Certainement,
il serait
préférable que le format de dépendance utilisé dès le début utilise le format de module ES2015. Mais si certaines dépendances ne le sont pas, cela ne vous empêche pas de déployer des projets à l'aide de modules en production.
Dans les parties suivantes de cet article, je vais vous montrer comment je collecte des projets dans des bundles qui utilisent des modules (y compris l'utilisation d'importations dynamiques et de séparation de code), je vais vous expliquer pourquoi ces solutions sont généralement plus productives que les scripts classiques, et montrer comment elles fonctionnent avec des navigateurs qui ne prennent pas en charge les modules.
Stratégie de création de code optimale
Le code du bâtiment pour la production est toujours une tentative d'équilibrer les avantages et les inconvénients de diverses solutions. D'une part, le développeur souhaite que son code se charge et s'exécute le plus rapidement possible. En revanche, il ne souhaite pas télécharger de code qui ne sera pas utilisé par les utilisateurs du projet.
De plus, les développeurs ont besoin de savoir que leur code est le mieux adapté à la mise en cache. Le gros problème du regroupement de code est que tout changement dans le code, même une ligne modifiée, entraîne l'invalidation du cache de l'ensemble du paquet. Si vous déployez une application composée de milliers de petits modules (présentés exactement sous la forme dans laquelle ils sont présents dans le code source), vous pouvez en toute sécurité apporter des modifications mineures au code et en même temps savoir que la plupart du code d'application sera mis en cache . Mais, comme je l'ai déjà dit, une telle approche du développement peut probablement signifier que le chargement du code la première fois que vous visitez une ressource peut prendre plus de temps que l'utilisation d'approches plus traditionnelles.
En conséquence, nous sommes confrontés à une tâche difficile, qui est de trouver la bonne approche pour diviser les faisceaux en plusieurs parties. Nous devons trouver le bon équilibre entre la vitesse de chargement des matériaux et leur mise en cache à long terme.
La plupart des bundlers utilisent par défaut des techniques de fractionnement de code basées sur des commandes d'importation dynamique. Mais je dirais que diviser le code uniquement en mettant l'accent sur l'importation dynamique ne permet pas de le diviser en fragments suffisamment petits. Cela est particulièrement vrai pour les sites avec de nombreux utilisateurs de retour (c'est-à-dire dans les situations où la mise en cache est importante).
Je pense que le code doit être divisé en fragments aussi petits que possible. Il vaut la peine de réduire la taille des fragments jusqu'à ce que leur nombre augmente tellement que cela affectera la vitesse de téléchargement du projet. Et bien que je recommande définitivement à chacun de faire sa propre analyse de la situation, si vous croyez aux calculs approximatifs effectués dans l'étude que j'ai mentionnée, lors du chargement de moins de 100 modules, il n'y a pas de ralentissement notable du chargement. Une étude distincte sur
les performances HTTP / 2 n'a pas révélé de ralentissement notable du projet lors du téléchargement de moins de 50 fichiers. Là, cependant, ils n'ont testé que des options dans lesquelles le nombre de fichiers était de 1, 6, 50 et 1000. En conséquence, probablement 100 fichiers sont une valeur à laquelle vous pouvez facilement naviguer sans craindre de perdre en vitesse de téléchargement.
Alors, quelle est la meilleure façon de diviser le code de manière agressive, mais pas trop agressive? En plus de diviser le code en fonction des commandes d'importation dynamique, je vous conseille de regarder de plus près le fractionnement du code en packages npm. Avec cette approche, ce qui est importé dans le projet à partir du dossier
node_modules
tombe dans un fragment séparé du code fini basé sur le nom du package.
Séparation des colis
J'ai dit ci-dessus que certaines des capacités modernes des bundlers permettent d'organiser un schéma haute performance pour le déploiement de projets basés sur des modules. Ce dont je parlais est représenté par deux nouvelles fonctionnalités de cumul. Le premier est
la séparation automatique du code via
import()
commandes d'
import()
dynamique
import()
(ajoutées
dans la v1.0.0 ). La deuxième option est
la séparation manuelle du code effectuée par le programme sur la
manualChunks
option
manualChunks
(ajoutée dans la
v1.11.0 ).
Grâce à ces deux fonctionnalités, il est désormais très facile de configurer le processus de construction, dans lequel le code est divisé au niveau du package.
Voici un exemple de configuration qui utilise l'option
manualChunks
, grâce à laquelle chaque module importé de
node_modules
tombe dans un fragment de code distinct dont le nom correspond au nom du package (techniquement, le nom du répertoire du package dans le dossier
node_modules
):
export default { input: { main: 'src/main.mjs', }, output: { dir: 'build', format: 'esm', entryFileNames: '[name].[hash].mjs', }, manualChunks(id) { if (id.includes('node_modules')) {
L'option
manualChunk
accepte une fonction qui accepte, comme argument unique, le chemin d'accès au fichier de module. Cette fonction peut renvoyer un nom de chaîne. Ce qu'il renvoie pointera vers un fragment de l'assembly auquel le module actuel doit être ajouté. Si la fonction ne renvoie rien, le module sera ajouté au fragment par défaut.
Considérons une application qui importe les
cloneDeep()
,
debounce()
et
find()
du
lodash-es
. Si vous appliquez la configuration ci-dessus lors de la création de cette application, chacun de ces modules (ainsi que chaque module
lodash
importé par ces modules) sera placé dans un seul fichier de sortie avec un nom comme
npm.lodash-es.XXXX.mjs
(ici
XXXX
est unique hachage du fichier module dans le fragment
lodash-es
).
À la fin du fichier, vous verrez une expression d'exportation comme celle-ci. Veuillez noter que cette expression ne contient que des commandes d'exportation pour les modules ajoutés au fragment, et pas tous les modules
lodash
.
export {cloneDeep, debounce, find};
Ensuite, si le code de l'un des autres fragments utilise ces modules
lodash
(peut-être uniquement la méthode
debounce()
), dans ces fragments, dans leur partie supérieure, il y aura une expression d'importation qui ressemble à ceci:
import {debounce} from './npm.lodash.XXXX.mjs';
Espérons que cet exemple clarifie la question du fonctionnement de la séparation manuelle du code dans le correctif cumulatif. De plus, je pense que les résultats de la séparation de code utilisant les expressions d'
import
et d'
export
sont beaucoup plus faciles à lire et à comprendre que le code de fragments, dont la formation a utilisé des mécanismes non standard qui ne sont utilisés que dans un certain bundler.
Par exemple, il est très difficile de comprendre ce qui se passe dans le fichier suivant. Ceci est la sortie d'un de mes anciens projets qui utilisait webpack pour diviser le code. Presque tout dans ce code n'est pas nécessaire dans les navigateurs qui prennent en charge les modules.
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["import1"],{ "tLzr": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "6xPP"); const import1 = "imported: " + _dep_1__WEBPACK_IMPORTED_MODULE_0__["dep1"]; }) }]);
Et s'il y a des centaines de dépendances npm?
Comme je l'ai dit, je crois que la séparation au niveau du code au niveau du package permet généralement au développeur de se placer dans une bonne position lorsque la séparation du code est agressive, mais pas trop agressive.
Bien sûr, si votre application importe des modules à partir de centaines de packages npm différents, vous pouvez toujours être dans une situation où le navigateur ne peut pas tous les charger efficacement.
Cependant, si vous avez vraiment de nombreuses dépendances npm, vous ne devez pas abandonner complètement cette stratégie pour l'instant. N'oubliez pas que vous ne téléchargerez probablement pas toutes les dépendances npm sur chaque page. Par conséquent, il est important de savoir combien de dépendances se chargent réellement.
Néanmoins, je suis sûr qu'il existe de vraies applications qui ont tellement de dépendances npm que ces dépendances ne peuvent tout simplement pas être représentées comme des fragments séparés. Si votre projet est juste cela - je vous recommande de chercher un moyen de regrouper les packages où le code dans lequel avec une forte probabilité peut changer en même temps (comme
react
et
react-dom
) car l'
react-dom
cache de fragments avec ces packages sera également exécutée en même temps. Plus tard, je montrerai un exemple dans lequel toutes les dépendances de React sont regroupées dans le même
fragment .
À suivre ...
Chers lecteurs! Comment abordez-vous le problème de la séparation du code dans vos projets?
