Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

4.3 — Gestion de paquets et environnements

🎯 Objectif : comprendre ce qui se passe quand tu fais npm install, gérer les conflits de versions, et savoir choisir entre npm, pnpm et yarn.

À l'issue de cet axe, tu sauras :

  • Lire et comprendre semver (^, ~, exacte)
  • Distinguer dependencies, devDependencies, peerDependencies
  • Expliquer le rôle du lockfile et savoir quand le commit / le supprimer
  • Choisir entre npm, pnpm, yarn classique, yarn berry
  • Mettre en place un monorepo avec workspaces

Débutant 10 min prérequis : axe 1 lu

{
"name": "mon-app",
"version": "1.4.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint ."
},
"dependencies": {
"react": "^19.0.0",
"zod": "~4.3.6"
},
"devDependencies": {
"vite": "^5.4.0",
"vitest": "^2.1.0",
"typescript": "^5.7.0"
},
"peerDependencies": {
"react": "^19.0.0"
},
"engines": {
"node": ">=20"
}
}
TypeQuand utiliser
dependenciesCode qui tourne en production (React, Express, lodash…)
devDependenciesOutils de dev (Vite, ESLint, Vitest, TypeScript…)
peerDependencies« Mon paquet a besoin de ce paquet, mais tu dois l’installer toi-même » (libs/plugins)

Erreur classique : mettre TypeScript en dependencies. Il sert à compiler avant le déploiement, donc c’est devDependencies.

Format : MAJOR.MINOR.PATCH (ex. 2.4.7).

NiveauQuand l’incrémenter
MAJOR (2 → 3)Changement incompatible (breaking change)
MINOR (2.4 → 2.5)Nouvelle fonctionnalité, compatible
PATCH (2.4.7 → 2.4.8)Correction de bug, compatible
SpécificateurSignifieExemple
19.0.0Exactement cette versionPin strict
^19.0.0>= 19.0.0 < 20.0.0 (compatible majeure)Défaut npm
~19.0.0>= 19.0.0 < 19.1.0 (compatible mineure)Plus prudent
* ou latestN’importe quoiÀ éviter
>= 19.0.0Au moins cette versionPour les peer deps
"version": "2.0.0-beta.3"
"version": "5.0.0-rc.1"
"version": "1.5.0-alpha.7"

Le caret n’inclut pas les pré-releases automatiquement : ^2.0.0 ne prendra pas 2.0.0-beta.3.

Quand tu fais npm install zod, npm ajoute ^4.3.6 au package.json. Mais quel sera exactement le numéro installé chez ton collègue dans 3 mois ?

Le lockfile (package-lock.json, pnpm-lock.yaml, yarn.lock) fige les versions exactes de tout l’arbre de dépendances :

package-lock.json
{
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-..."
}
// + toutes les transitives
}

Règle absolue : commit ton lockfile. Sinon ton équipe et ton CI installent des versions différentes, et tu chasses des bugs fantômes.

CommandeComportement
npm installMet à jour le lockfile si différent du package.json
npm ciInstalle exactement ce qui est dans le lockfile, jamais ne le modifie

En CI/CD : toujours npm ci. En local pour ajouter une dep : npm install.

npmpnpmyarn classicyarn berry (v4+)
Vitesse install🟡🟢🟢🟢🟢🟢🟢
Espace disque (node_modules)🔴 dupliqué🟢 lien dur🔴 dupliqué🟢 PnP
Lockfilepackage-lock.jsonpnpm-lock.yamlyarn.lockyarn.lock
Workspaces✅ excellent
Compatibilité écosystème🟢 totale🟢 totale🟢 totale🟡 PnP peut casser certains outils
Maturité 2026🟢🟢🟢🟢

Recommandation 2026 :

  • pnpm : meilleur défaut moderne. Rapide, économe en disque, bonne gestion des monorepos.
  • npm : si tu veux le strict minimum officiel et zéro outil supplémentaire.
  • yarn berry : si l’équipe le maîtrise — l’écosystème PnP peut surprendre.
Fenêtre de terminal
# Installer pnpm
npm install -g pnpm
# Migrer un projet npm → pnpm
rm -rf node_modules package-lock.json
pnpm install

Depuis Node.js 24 (LTS depuis octobre 2025, courant en 2026), une nouveauté change la donne pour les projets TypeScript : le type stripping natif.

Fenêtre de terminal
# Avec Node 24+
node hello.ts # ça marche directement, sans tsx ni ts-node

Sous le capot, Node fait du type stripping : il enlève types, interfaces et autres constructions TS, et exécute le JS résultant. C’est activé par défaut pour les .ts (auparavant via --experimental-strip-types).

Ce que ça apporte :

  • Plus besoin de tsx/ts-node pour les scripts simples et les outils maison.
  • Démarrage plus rapide qu’un transpileur en watch.
  • TypeScript devient une « surcouche zéro-coût » à l’exécution.

Limites importantes :

  • Pas de type-checking au runtime (Node strip les types, ne les valide pas) → garde tsc --noEmit ou astro check en CI.
  • Subset autorisé : pour un strip purement syntaxique, certaines constructions TS sont interdites (enum traditionnels, namespaces, casts spécifiques). TypeScript 5.8 a introduit l’option --erasableSyntaxOnly qui te dit d’avance si ton code est compatible.
  • En production, on recommande toujours un build TS avec optimisations (sourcemaps, tree-shaking, target précis).
// tsconfig.json — pour vérifier la compatibilité Node-strip
{
"compilerOptions": {
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"target": "ES2023"
}
}

Tu travailleras sur des projets en Node 20, 22, 24… Solution : un gestionnaire de versions.

Fenêtre de terminal
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
nvm install 24 # installer Node 24 (LTS courant)
nvm install 22 # installer Node 22 aussi
nvm use 24 # basculer
nvm alias default 22 # version par défaut

Différent projet : github.com/coreybutler/nvm-windows

Fenêtre de terminal
# Installer
curl -fsSL https://fnm.vercel.app/install | bash
# ou : winget install Schniz.fnm
fnm install 22
fnm use 22

Fichier à la racine du projet qui dit quelle version utiliser :

22.10.0

nvm use le lit automatiquement. Les CI le respectent aussi (GitHub Actions a actions/setup-node qui le détecte).

Les scripts sont des commandes shell exécutables via npm run (ou pnpm/yarn).

{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test": "vitest",
"test:watch": "vitest --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"typecheck": "tsc --noEmit",
"ci": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
}
}
{
"scripts": {
"prebuild": "pnpm typecheck",
"build": "vite build",
"postbuild": "pnpm test:e2e"
}
}

pnpm build exécute alors prebuildbuildpostbuild automatiquement.

Fenêtre de terminal
npm run test -- --watch # double tiret = arguments au script
pnpm test --watch # pnpm n'a pas besoin du double tiret

Plusieurs paquets dans un seul repo, dépendances internes partagées.

pnpm-workspace.yaml à la racine :

packages:
- "apps/*"
- "packages/*"

Structure :

mon-monorepo/
├── apps/
│ ├── web/
│ │ └── package.json (Next.js)
│ └── mobile/
│ └── package.json (React Native)
├── packages/
│ ├── ui/
│ │ └── package.json (composants partagés)
│ └── config/
│ └── package.json (eslint config, tsconfig)
├── pnpm-workspace.yaml
└── package.json
Fenêtre de terminal
# Installer une dep dans un workspace spécifique
pnpm add react --filter web
# Lancer un script dans tous les workspaces
pnpm -r build
# Référencer un workspace interne dans un autre
# Dans apps/web/package.json :
"dependencies": {
"@mon-monorepo/ui": "workspace:*"
}
  • Turborepo : cache et parallélisation des scripts (Vercel).
  • Nx : équivalent plus complet, orienté entreprise.
Fenêtre de terminal
npm install -g serve # installe globalement

Problème : la version globale est partagée par tous tes projets — un conflit fait des ravages. Préfère :

Fenêtre de terminal
npx serve # exécute sans installer
pnpm dlx serve # équivalent pnpm

Ou installe-le en devDependencies du projet et accède via un script npm.

Tu vois "react": "^19.0.0" dans package.json. Quelle version sera installée si tu fais npm install demain ?
Pourquoi `npm ci` est préféré à `npm install` en CI/CD ?
TypeScript dans une app React. Où le mettre ?

Suite : 4.4 — Conteneurisation (Docker) — l’environnement reproductible au niveau du système.