La semaine dernière, les développeurs de Yarn (le gestionnaire de paquets pour Javascript) ont annoncé une nouvelle fonctionnalité - l'installation de Plug'n'Play. Cette fonctionnalité vous permet d'exécuter des projets Node.js sans utiliser le dossier node_modules, dans lequel les dépendances de projet sont généralement installées avant de démarrer. La description de la fonctionnalité déclare que les modules node_modules ne seront plus nécessaires - les modules seront chargés à partir du cache général du gestionnaire de packages.
Dans le même temps, les développeurs de NPM ont également annoncé leur solution similaire au problème.
Examinons de plus près ces solutions et essayons de les tester dans de vrais projets.
Historique du problème
Initialement, le système modulaire NodeJS était entièrement basé sur le système de fichiers. Tout appel à require()
mappé au système de fichiers. Pour organiser les modules tiers, le dossier node_modules a été inventé, dans lequel les modules et les bibliothèques réutilisables doivent être téléchargés et installés. Ainsi, chaque projet a reçu son propre ensemble distinct de dépendances, gaspillant ainsi l'espace disque de manière inefficace.
L'installation de dépendances prend la plupart du temps de construction dans les systèmes CI, donc l'accélération de cette étape affectera favorablement le temps de construction dans son ensemble.
Simplifiée, l'installation des modules comprend les étapes suivantes:
- La version spécifique du module est calculée à partir de l'intervalle valide.
- Tous les modules des versions requises sont téléchargés à partir du référentiel et stockés dans le cache local
- Les modules du cache local sont copiés dans le dossier node_modules du projet
Si les deux premières étapes sont déjà suffisamment optimisées et sont effectuées rapidement, lorsque vous avez déjà des modules mis en cache, la troisième étape reste presque inchangée par rapport aux premières versions de node et npm.
La nouvelle approche propose de se débarrasser de la troisième étape et de remplacer la copie réelle des fichiers en créant une table qui mappera les modules demandés à leurs copies dans le cache local.
Utiliser des liens symboliques
Au lieu de copier réellement les modules, vous pouvez ajouter un lien symbolique vers leur emplacement dans le cache. Cette approche est implémentée dans PNPM , un autre gestionnaire de packages alternatif. L'approche peut bien fonctionner, mais avec les liens symboliques, de nombreux problèmes sont associés à la double localisation du fichier, à la recherche de modules adjacents, etc. De plus, la création de liens symboliques est une opération de fichier que j'aimerais éviter de manière idéale de travailler.
Essayer le fil PNP
Vous pouvez en savoir plus sur cette fonctionnalité dans la description officielle . Ce paragraphe contient son bref récit.
La version PNP de Yarn est désormais en fonction-branche fil-pnp .
Nous clonons le référentiel localement avec la branche souhaitée
git clone git@github.com:yarnpkg/yarn.git --branch yarn-pnp
L'instruction d'assemblage du fil est ici , l'ensemble des étapes est très trivial.
Une fois la construction terminée, ajoutez un alias à la version personnalisée de yarn et commencez à travailler avec:
alias yarn-local="node $PWD/lib/cli/index.js"
Plug'n'play est activé de deux manières: soit par le drapeau: yarn --pnp
, soit par une configuration supplémentaire dans package.json
: "installConfig": {"pnp": true}
.
À titre d'exemple, les développeurs de Yarn ont déjà préparé un projet de démonstration . Il a Webpack, Babel et d'autres outils typiques d'un frontal moderne. Essayons d'établir ses dépendances de différentes manières et obtenons les résultats suivants:
- Installation de
yarn
typique: 19 s - Installation via
yarn --pnp
: 3s
Avant la mesure, une installation à froid a été réalisée afin que tous les modules nécessaires soient déjà dans le cache.
Voyons comment cela fonctionne. Après une installation pnp, un fichier .pnp.js
supplémentaire est créé à la racine du projet qui contient un remplacement de la logique native dans la classe Module intégrée à Node.js. En chargeant ce fichier dans notre code, nous donnons à la fonction require()
la possibilité d'obtenir des modules à partir du cache global et de ne pas regarder node_modules
. Toutes les commandes de fil intégrées, telles que le yarn start
ou le yarn test
, préchargent ce fichier par défaut, vous n'aurez donc pas besoin de modifier votre code si vous avez déjà utilisé le fil auparavant.
En plus de mapper les modules, pnp.js effectue une validation de dépendance supplémentaire. Si vous essayez d'appeler require('test')
, sans dépendance déclarée dans package.json
, vous obtenez l'erreur suivante: Error: You cannot require a package ("test") that is not declared in your dependencies
. Cette amélioration devrait augmenter la fiabilité et la prévisibilité du code.
Parmi les inconvénients de la nouvelle approche, il convient de noter qu'une intégration supplémentaire sera nécessaire pour les outils qui fonctionnaient directement avec le répertoire node_modules sans les mécanismes Node intégrés. Par exemple, Webpack et d'autres constructeurs frontaux auront besoin de plug-ins supplémentaires pour pouvoir trouver les fichiers nécessaires au regroupement.
Dans le projet de démonstration, il existe des esquisses de résolveurs pour Eslint, Jest, Rollup et Webpack.
Dans mon expérience, il y avait toujours des problèmes avec Typescript, qui est très lié à la présence de node_modules et il n'y a pas de moyen facile de remplacer la stratégie de recherche de module.
Il y aura également des problèmes avec les scripts postintall. Étant donné que le module reste dans le cache, les scripts de post-installation qui changent son état (par exemple, téléchargent des fichiers supplémentaires) peuvent endommager le cache et interrompre d'autres projets qui en dépendent. Les développeurs de Yarn recommandent de désactiver l'exécution des scripts avec l' --ignore-scripts
. Ils avaient déjà expérimenté l'activation de ce drapeau par défaut pour tous les projets sur Facebook et n'ont trouvé aucun problème sérieux. À long terme, l'abandon des scripts de post-installation semble être une bonne étape compte tenu des problèmes de sécurité connus.
Essayer le bricolage NPM
L'équipe NPM a également annoncé sa solution alternative. Leur nouvel outil, tink, est livré avec un module distinct, indépendant du NPM. Tink reçoit le fichier package-lock.json
en package-lock.json
, qui est généré automatiquement lorsque l' npm install
exécutée. Sur la base du fichier de verrouillage, tink génère un node_modules/.package-map.json
, qui stocke la projection des modules locaux sur leur emplacement réel dans le cache.
Contrairement à Yarn, il n'y a pas de fichier de hook qui puisse être préchargé dans votre projet pour nécessiter un patch. Au lieu de cela, il est suggéré d'utiliser la commande tink
au lieu de node
pour obtenir le bon environnement. Cette approche est moins ergonomique car elle nécessitera des modifications de votre code pour le faire fonctionner. Cependant, comme le fera une preuve de concept.
J'ai essayé de comparer la vitesse d'installation des modules avec les commandes npm ci
et tink
, mais tink était encore plus lent, donc je ne donnerai pas les résultats. Évidemment, ce projet est beaucoup plus brut par rapport à Yarn et n'est pas du tout optimisé. Eh bien, nous attendrons les nouvelles versions.
Conclusion
Le rejet du répertoire node_modules est une étape logique, compte tenu de l'expérience d'autres langages, où cette approche n'était pas là au départ. Cela affectera favorablement la vitesse de construction avec les systèmes CI, où il est possible de sauvegarder le cache du package entre les générations. De plus, si vous transférez le cache du package et le fichier .pnp.js
d'un ordinateur à un autre, vous pouvez reproduire l'environnement sans même démarrer Yarn. Cela peut être utile dans les systèmes de construction de conteneurs: montez le répertoire avec le cache, placez le fichier .pnp.js
et vous pouvez immédiatement exécuter les tests.
La nouvelle approche semble inhabituelle et rompt certaines pratiques établies en raison du fait que tous les modules sont toujours disponibles dans node_modules. Mais le fichier .pnp.js
propose une API qui vous permet d'abstraire de la position réelle des fichiers et de travailler avec l'arborescence virtuelle. De plus, en dernier recours, il existe une commande yarn unplug --persist
qui extrait un module du cache et le place localement dans node_modules
.
En tout cas, rien n'est encore finalisé, même la pull-request en Yarn n'a pas encore été déversée, il faut s'attendre à des changements. Mais c'était intéressant pour moi d'essayer la version alpha de la fonctionnalité dans la pratique et de les tester sur quelques-uns de mes projets personnels et de m'assurer que cette approche fonctionne vraiment, ce qui rend l'installation plus rapide.
Les références