npm 7.0 fait sa révolution : workspaces, npx, dépendances et autres améliorations

npm 7.0 fait sa révolution : workspaces, npx, dépendances et autres améliorations

Mais toujours centralisé

Avatar de l'auteur
David Legrand

Publié dans

Logiciel

21/10/2020 8 minutes
7

npm 7.0 fait sa révolution : workspaces, npx, dépendances et autres améliorations

npm 7.0 est là, avec une grande part de travail de réécriture de la part de l'équipe qui a mis en place des modules pour certains presque entièrement nouveaux, bien que reprenant les fonctionnalités de base du client en lignes de commandes (CLI). On fait le point sur les améliorations et changements.

En mars, GitHub (et donc Microsoft) se payait npm. Derrière ces trois lettres se cache le gestionnaire de paquet le plus populaire de l'écosystème JavaScript, cohabitant avec le yarn de Facebook. Une sorte de commun selon C J Silverio, ancienne CTO de npm Inc., qui regrettait l'année dernière le point de centralisation qu'il représente.

Les objectifs affichés à l'époque étaient de renforcer l'outil et le rendre plus pratique, tout en préservant sa gratuité d'accès pour tous. C'est peu après que le  travail sur la CLI 7.0 de npm a réellement commencé, bien qu'en gestation depuis des mois. De l'aveu même de son créateur, Isaac Z. Schlueter, le merge effectué la semaine dernière était le plus important depuis longtemps : 4 142 fichiers modifiés, 203 316 ajouts et 310 411 suppressions.

Preuve qu'il ne s'agit pas d'une évolution mineure, loin de là. Car npm avait besoin d'un bon nettoyage de printemps. Mais aussi de revenir en profondeur sur certains points qui étaient devenus bloquants avec le temps. Notamment sa gestion de l'ensemble des dépendances, dont la refonte a donné naissance à Arborist. 

Une migration en douceur pour éviter de tout casser

L'ampleur est telle que npm 7.0 n'est pas encore considérée comme latest, ce qui pousserait à la mise à jour. Outre des changements qui risquent de casser la compatibilité, l'équipe veut attendre encore des retours IRL avant de sauter le pas. Node.js 15, qui vient d'être publiée, est la première version livrée avec npm 7.0.

Il faut dire que npm est aujourd'hui utilisé par plusieurs millions de développeurs à travers le monde. Le service héberge pas moins de 1,3 million de paquets et comptabilise 75 milliards de téléchargements par mois. Lorsque des changements importants ont lieu, ils ne peuvent donc pas être effectués à la légère. 

Ce qui explique que l'équipe communique sur sa v7 depuis mai dernier et a débuté la publication de ses bêta en août. 13 versions et 4 release candidates ont été nécessaires. Ces précautions n'ont pas empêché les trouvailles de dernière minute. Ainsi, deux correctifs ont déjà été publiés. La dernière version en date est ainsi la 7.0.3.

Si vous n'êtes pas encore sur Node.js 15, il faut pour le moment taper cette commande pour y passer :

npm install -g npm@7

Intégration de npx et autres petites améliorations

Avant de nous attaquer aux gros morceaux, commençons par les multiples changements qui font l'attrait de npm 7. Tout d'abord tout le travail de refactorisation. Plusieurs fonctionnalités ont été externalisées dans des modules, comme Arborist sur lequel nous reviendrons plus loin. De quoi produire un code plus fiable et facile à maintenir.

Le développeur Ruy Adorno évoque ainsi la refonte de config ou run entre autres nouveautés et passes de nettoyage. Autre bonne nouvelle, npm audit doit produire une sortie plus facile à lire, dans sa version « humaine » ou JSON. L'équipe dit vouloir publier des mises à jour plus régulières grâce à un cycle d'itération plus court.

Elle promet que malgré des évolutions de portions importantes du code, la compatibilité avec les modules actuels est maintenue. Pourtant, certaines habitudes vont devoir changer. L'une d'entre elles est la commande npx qui est remplacée. Très utilisée, elle permet d'exécuter un module. Elle est désormais intégrée à la CLI.

Entièrement réécrite, elle comporte quelques modifications comme le fait de vous demander si vous voulez télécharger un module qui n'est pas présent sur le système plutôt que de le faire automatiquement. Son intégration implique qu'elle réponde désormais à une autre commande :

npx cowsay Meuuuh !

Devient ainsi : 

npm exec cowsay Meuuuh !

Pour ne pas trop perturber les développeurs, npx reste présent sous la forme d'un alias. Un autre peut être utilisé selon l'aide : npm x. Voici le contenu de cette dernière, puisque la documentation ne semble pas encore à jour :

npm exec -- <pkg>[@<version>] [args...]
npm exec --package=<pkg>[@<version>] -- <cmd> [args...]
npm exec -c '<cmd> [args...]'
npm exec --package=foo -c '<cmd> [args...]

npx <pkg>[@<specifier>] [args...]
npx -p <pkg>[@<specifier>] <cmd> [args...]
npx -c '<cmd> [args...]'
npx -p <pkg>[@<specifier>] -c '<cmd> [args...]

alias: x
common options:
--package=<pkg> (may be specified multiple times)
-p is a shorthand for --package only when using npx executable
-c <cmd> --call=<cmd> (may not be mixed with positional arguments)

Workspaces, yarn.lock : l'esprit de Facebook rôde

Le projet yarn est né de la volonté de proposer une alternative à npm, s'en différenciant par certains aspects, comme sa rapidité. Nous avions d'ailleurs vu dans notre article évoquant pnpm que des mécaniques permettaient d'améliorer les choses sans avoir à changer de gestionnaire. Son intégration n'est pas au menu. 

Mais des réflexions sont en cours sur le sujet. Dans un billet de blog dédié, l'équipe explique travailler « à une approche de type système de fichiers virtuel pour npm v8, sur le modèle de Tink, la preuve de concept que Kat Marchán a rédigé en 2019. Nous avons aussi évoqué le fait de migrer sur quelque chose s'approchant de la structure de pnpm, bien que ce serait d'une certaine manière un plus gros changement encore que Tink ».

Le projet Tink était présenté à la JSConf 2019 :

Tink veut intégrer la gestion de paquets au runtime et simplifier le workflow des développeurs

Le fichier yarn.lock contenant les informations des dépendances (version, résolution) est désormais pris en charge par npm. S'il existe au sein du projet, il pourra être utilisé pour extraire des informations et être modifié si nécessaire. Mais il coexistera avec le package-lock.json (v2) de npm que l'équipe juge plus complet et efficace.

Autre fonctionnalité de yarn (et pnpm) a faire son entrée : les Workspaces. Pour faire simple, il s'agit d'organiser plusieurs paquets de sorte qu'ils forment un même et unique projet, chacun avec son manifeste (package.json) et ses dépendances. C'était une demande forte de la communauté, qui a nécessité une modification dans la gestion des dépendances. Notamment parce que chaque module peut dépendre ou non d'un autre :

├── package.json { "workspaces": ["dep-a", "dep-b"] }
├── dep-a
│   └── package.json { "dependencies": { "dep-b": "^1.0.0" } }
└── dep-b
  └── package.json { "version": "1.3.1" }

Dans le projet ci-dessus on voit un manifeste global qui déclare deux workspaces, le premier dépendant du second. Le résultat sera un ensemble de dossiers et de fichiers organisés de la sorte :

├── node_modules
│ ├── dep-a -> ./dep-a
│ └── dep-b -> ./dep-b
├── dep-a
└── dep-b

Ils apparaitront bien dans node_modules, mais seulement sous la forme de liens symboliques renvoyant aux dossiers propres à chaque workspace. Dans la pratique, leur déclaration se fait comme suit :

{
    "name": "foo",
    "version": "1.0.0",
    "workspaces": [
        "./core/*",
        "./packages/*"
    ],
    dependencies: {
        "lodash": "^4.x.x",
        "libnpmutil": "^1.0.0"
    }
}

Et pour l'un des workspaces qui dépendrait d'un autre :

{
"name": "workspace-c",
"version": "1.0.0",
"peerDependencies": {
"react": "^16.x.x"
},
"dependencies": {
"workspace-b": "^1.0.0"
}
}

L'organisation d'un tel projet, plus complexe, donnerait le résultat suivant :

├── package-lock.json
├── node_modules
│ ├── lodash
│ ├── libnpmutil -> ./core/libnpmutil
│ ├── workspace-a -> ./packages/workspace-a
│ ├── workspace-b -> ./packages/workspace-b
│ ├── workspace-c -> ./packages/workspace-c
│ └── react
├── core
│ └── libnpmutil
└── packages
├── workspace-a
├── workspace-b
└── workspace-c
└── node_modules
└── [email protected]

Arborist et Peer dependencies

Les plus attentifs auront noté la mention de peer dependencies. Une fonctionnalité qui fait son retour au sein de npm (après un abandon en v4). C'est en fait l'ensemble qui a nécessité le plus de travail, car lié au nouveau module Arborist, qui traite les dépendances d'un projet (peer ou non), et donc ses workspaces.

Ce module a été la plus grosse externalisation et réécriture de code de npm 7. « Parce que s'il est tentant de parler d'un arbre des dépendances [...] il s'agit plutôt d'un graph » indique l'équipe pour que l'on mesure toute la complexité des liens qu'il peut exister entre certains modules au sein d'un projet.

Elle a donc entièrement repensé l'organisation et la gestion des modules sous la forme de nœuds, de liens et de relations (edge). Il est aussi possible désormais de demander à favoriser la déduplication (--prefer-dedup), c'est-à-dire à n'utiliser qu'une même version d'un module au sein d'un projet plutôt que plusieurs différentes.

Concernant les peer dependencies, il s'agit donc à nouveau d'installer automatiquement les modules qui peuvent être rendus nécessaires par les modules utilisés dans un projet. Auparavant il fallait prendre soin de les installer manuellement, une manière pour npm de se retirer une épine du pied en termes de complexité.

Il est possible de s'en passer avec l'argument --legacy-peer-deps. Il sera d'ailleurs implicite dans la commande npm ls qui fait la liste des dépendances. Pour voir toutes celles prises en compte, tapez npm ls --all.

7

Écrit par David Legrand

Tiens, en parlant de ça :

Sommaire de l'article

Introduction

Une migration en douceur pour éviter de tout casser

Intégration de npx et autres petites améliorations

Workspaces, yarn.lock : l'esprit de Facebook rôde

Arborist et Peer dependencies

Commentaires (7)


Node, tu part de qques dizaines de Ko de source, et te retrouve avec une vingtaine de millers de fichiers dépendant et 300 Mo dans nodes_modules, tu ajoute Docker et te voilà à exploser les compteurs de CO 2 :transpi:


En fait, le plus souvent, ce sont les devDependencies qui sont massives, avec tous les outils de test/lint/build/optimisation du projet pour travailler from source. D’autant que la reco a longtemps été (et est peut-être encore d’ailleurs) de fixer les versions attendues de ces outils et d’en faire une install par projet (pour éviter les soucis de build dépendants du setup individuel des devs)



Mais ce n’est généralement pas ce qu’on va déployer sur les serveurs à la fin ( et heureusement xD ).



Pour essayer de réduire un peu cet effet, les gros frameworks viennent de plus en plus avec leur propre client en ligne de commande, que tu installes hors du projet et qui contient toute la machinerie de build (ce qui est très bien tant que tu utilises la même build suite que le framework)



Après, garder sous contrôle les dépendances externes du projet en lui-même, c’est un travail permanent d’audit, de revue, et de construction de bonnes habitudes, mais ça se fait.



spidermoon a dit:


Node, tu part de qques dizaines de Ko de source, et te retrouve avec une vingtaine de millers de fichiers dépendant et 300 Mo dans nodes_modules, tu ajoute Docker et te voilà à exploser les compteurs de CO 2 :transpi:




En pratique ça dépend complètement du développeurs.
À toi de bien choisir tes dépendances, si tu prends tout et n’importe quoi sans réfléchir même 1s, en effet tu as des milliers de libs et c’est n’importe quoi, et sans troller, c’est ce qu’on rencontre souvent avec les jeunes dev (troll triste: je suis devops fullstack mais je sais pas en donner la définition)



Très concrètement chez nous on dev des apps node pour notre produit en mode micro-service, de la plus petite à quelques-unes vraiment massives, y’en a pas une qui dépasse les 10 dépendances externes directes, et je viens de regarder, la pire avec toutes les sous dépendances il y en a 29 (mais par exemple la lib PG est éclatée en sous libs et ce lots d’un seul sujet fait 7 dépendances qui en réalité en est qu’une).
Pour finir d’appuyer, une partie non négligeable n’ont que deux dépendances externes.



Mon avis est, comme dans tout système où tu es libre de faire, il y a des gens raisonnables, et il y a les autres.



Conclusion, il ne faut pas dénigrer le système, seulement les gens qui l’utilisent mal.


Surtout que pour le coup, npm détaille la taille des paquets, leurs dépendances (dev ou non), etc. On peut sans doute regretter que ce ne soit pas détaillé dans les résultats de la recherche, mais on fait son choix en conscience si l’on regarde la fiche des paquets que l’on utilise.


C’est aussi inattendu que génial de voir des articles techniques comme celui-ci aller autant dans le détail avec une vraie recherche derrière ! Bravo et merci :-)
(ça fait peut être bizarre aux lecteurs non-dévs s’ils s’attendent à des articles plus généralistes ?)


Il en faut pour tous les goûts ;)


npm config avait besoin de nettoyage. Certaine config sont chargé depuis le fichier .npmrc d’autre depuis la configuration poussé avec ‘npm config set’. C’est à se mordre les doigts quand on build/charge des packages privée dans une CI.