pnpm : ce qu'il apporte, et en quoi il diffère de npm, Yarn et Bun
Guide complet et sourcé du gestionnaire de paquets pnpm : store content-addressable et liens durs, structure non plate de node_modules, vitesse, monorepos et catalogs, sécurité supply-chain, comparaison détaillée avec npm, Yarn (Classic et PnP) et Bun, et nouveautés pnpm 11.
Conversation ayant produit cet article
Cet article complète Bibliothèques de composants partagées : les stratégies qui existent quand on a 300 projets. Cet article-là posait les paquets npm et le monorepo comme stratégies de partage ; celui-ci descend d’un cran, au niveau de l’outil qui installe ces paquets.
Les quatre gestionnaires de paquets JavaScript du moment — npm, Yarn, pnpm, Bun — parlent tous au même registre (registry.npmjs.org), lisent le même package.json, et installent les mêmes tarballs. Sur le papier, ils sont interchangeables. En pratique, ils diffèrent radicalement sur trois points qui décident de votre quotidien : comment ils posent les fichiers sur le disque, à quelle vitesse, et quelles garanties de correction et de sécurité ils vous donnent.
pnpm est devenu, depuis ~2023, le choix par défaut des projets sérieux et des monorepos. Cet article explique pourquoi, en disséquant ce qu’il fait différemment et en le confrontant point par point à npm, Yarn et Bun.
1. Le problème commun : ce n’est pas le registre, c’est l’algorithme
Tous ces outils résolvent le même problème : transformer une liste de dépendances déclarées en un arbre node_modules que Node.js saura résoudre. Node, lui, applique une règle simple : pour résoudre require('foo') (ou import 'foo'), il remonte les dossiers node_modules parents jusqu’à trouver foo.
C’est cette règle de résolution qui ouvre la porte à plusieurs stratégies d’agencement de node_modules, et c’est là que les outils divergent. On distingue quatre modèles :
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Plat (hoisted) │ │ Symlinks + CAS │ │ Isolé (linker) │ │ Plug'n'Play │
│ npm, Yarn Class.│ │ pnpm │ │ Bun │ │ Yarn Berry │
│ Bun (défaut) │ │ │ │ (workspaces v1) │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────────┘
tout remonté arbre strict arbre strict pas de
à la racine via symlinks via symlinks node_modules
Le reste de l’article détaille ces modèles et leurs conséquences.
2. node_modules plat : le modèle npm/Yarn Classic et ses fuites
Quand npm ou Yarn Classic installent express, ils ne posent pas seulement express/ dans node_modules : ils remontent (hoist) toutes les dépendances transitives au premier niveau.
node_modules/
├── accepts ← transitive d'express
├── body-parser ← transitive d'express
├── cookie ← transitive d'express
├── debug ← transitive d'express
├── express ← votre dépendance directe
└── ... (des dizaines d'autres, toutes remontées)
Source : pnpm — Flat node_modules is not the only way
Ce remontage existe pour une bonne raison historique : dédupliquer. Sans lui, chaque dépendance aurait sa propre copie de ses transitives, et l’arbre exploserait (c’était le cas de npm v2, avec des chemins si profonds que Windows plantait). Mais le remontage crée deux pathologies.
Les dépendances fantômes (phantom dependencies)
Comme debug ou cookie se retrouvent à la racine de node_modules, votre code peut faire require('debug') alors que vous ne l’avez jamais déclaré dans votre package.json. Ça marche… jusqu’au jour où express change de version et ne tire plus debug. Votre import casse sans qu’aucune de vos dépendances directes n’ait bougé.
Le code n’a accès qu’à ce qui est physiquement à la racine de
node_modules, pas à ce qui est déclaré. npm et Yarn Classic ne font donc respecter aucune frontière entre « ce que j’utilise » et « ce que mes dépendances utilisent ».
Les doublons (doppelgangers)
Le remontage ne peut hisser qu’une seule version d’un paquet à la racine. S’il y a conflit de versions (A veut lodash@3, B veut lodash@4), l’une reste à la racine, l’autre est dupliquée plus profond dans l’arbre. Sur un gros projet, on se retrouve avec plusieurs copies physiques du même paquet, et l’ordre de remontage est non déterministe d’une installation à l’autre.
Source : pnpm — Motivation
3. La réponse de pnpm : store content-addressable + liens durs + symlinks
pnpm refuse le modèle plat. Sa structure repose sur deux mécanismes empilés.
3.1 Un store global unique, adressé par contenu
pnpm maintient un seul store sur la machine (par défaut ~/.local/share/pnpm/store), partagé par tous les projets. Chaque fichier y est stocké une fois, indexé par le hash de son contenu (content-addressable storage, CAS).
Conséquence directe sur le disque :
- Si 100 projets dépendent de
lodash@4.17.21, il n’y a qu’une copie sur le disque. npm en aurait 100. Source : pnpm — Motivation - Si une nouvelle version de
lodashne change qu’un fichier sur 100,pnpm updaten’ajoute qu’un seul fichier au store, pas une copie complète du paquet.
L’adressage par contenu implique aussi la déduplication au niveau du fichier, pas du paquet : deux paquets différents qui contiennent un LICENSE identique partagent le même objet dans le store.
3.2 Liens durs vers le projet, symlinks pour l’arbre
Le store ne sert à rien si Node ne sait pas l’atteindre. pnpm procède en deux temps :
- Les fichiers du store sont liés en dur (hard link) dans un dossier plat caché
node_modules/.pnpm/. Un lien dur ne coûte aucun espace disque : c’est la même donnée sur disque, deux entrées de répertoire. - Les paquets visibles depuis votre code sont des liens symboliques (symlinks) vers ces emplacements.
node_modules/
├── express -> ./.pnpm/express@5.1.0/node_modules/express ← symlink, dép. directe
└── .pnpm/
├── express@5.1.0/node_modules/
│ ├── express/ ← liens durs vers le store
│ ├── debug -> ../../debug@4.4.0/node_modules/debug ← transitive d'express
│ └── ...
└── debug@4.4.0/node_modules/debug/ ← liens durs vers le store
Source : pnpm — Symlinked node_modules structure
Le point clé : seules vos dépendances directes apparaissent à la racine de node_modules. debug n’y est pas, il est confiné au sous-arbre d’express. Donc require('debug') depuis votre code échoue — exactement comme il le devrait. pnpm élimine les dépendances fantômes par construction.
3.3 La résolution des peer dependencies
pnpm pousse la rigueur jusqu’aux peer dependencies. Un paquet qui dépend de peers différents selon le contexte est dupliqué (par lien dur, donc gratuitement) avec un suffixe encodant les peers résolus :
.pnpm/
├── foo@1.0.0_react@16.7.0/ ← foo résolu avec react 16
└── foo@1.0.0_react@17.0.0/ ← foo résolu avec react 17
Source : pnpm — How peers are resolved
C’est ce qui donne à pnpm une correction que les autres n’ont pas, au prix d’une structure que certains outils mal écrits (qui supposent un node_modules plat) ne digèrent pas — voir la section Pièges.
4. La vitesse
L’installation se décompose en trois étapes : résoudre l’arbre, télécharger les paquets manquants vers le store, et lier le store vers node_modules. pnpm parallélise ces étapes (un paquet peut être lié pendant qu’un autre est encore téléchargé), là où npm/Yarn Classic enchaînent plus séquentiellement.
Source : pnpm — Motivation
Les benchmarks varient selon le scénario (cache froid/chaud, présence du lockfile, node_modules déjà là), mais les ordres de grandeur convergent :
| Scénario | npm | Yarn Classic | pnpm | Bun |
|---|---|---|---|---|
| Install propre (cache + lockfile froids) | lent (référence) | ~ npm | 2–3× plus rapide que npm | le plus rapide |
| Install avec cache + lockfile | moyen | moyen | rapide | très rapide |
| Disque occupé | élevé | élevé | −50 à −70 % | faible |
Bun est, en absolu, le plus rapide à l’installation : sur un projet de ~50 dépendances, de l’ordre de 17× plus rapide que npm et ~5× plus rapide que pnpm, son installeur étant écrit en Zig et fortement optimisé (syscalls bas niveau, parallélisme agressif). pnpm offre, lui, le meilleur compromis vitesse/disque/compatibilité.
Sources : DeployHQ — Choosing the right package manager, edbzn/package-manager-benchmarks, pnpm — Benchmarks
En pratique, pour la perception « install rapide au quotidien », les trois challengers (Yarn moderne, pnpm, Bun) se valent une fois le store/cache chaud. La vraie différence de vitesse de npm se voit sur les installs CI à froid et sur les monorepos.
5. Monorepos : workspaces, filtres et catalogs
C’est sur les monorepos que pnpm creuse le plus l’écart, parce que ses deux atouts (store partagé, structure stricte) y rendent le plus.
5.1 Workspaces
pnpm déclare ses workspaces dans un fichier dédié, pnpm-workspace.yaml (et non dans package.json comme npm/Yarn) :
packages:
- "packages/*"
- "apps/*"
Les dépendances locales entre paquets du monorepo sont liées par symlink via le protocole workspace: ("@org/ui": "workspace:*"), garantissant qu’on consomme toujours la version locale, jamais une version publiée du registre.
5.2 Filtres
--filter cible une partie du monorepo sans tout reconstruire :
pnpm --filter @org/api build # ce paquet seul
pnpm --filter @org/api... build # ce paquet + ses dépendances
pnpm --filter ...@org/api build # ce paquet + ce qui en dépend
pnpm --filter "./apps/**" test # par chemin
5.3 Catalogs : versions partagées
Fonctionnalité propre à pnpm : les catalogs centralisent les versions de dépendances communes à tout le monorepo dans pnpm-workspace.yaml, et chaque paquet les référence par le protocole catalog:.
# pnpm-workspace.yaml
catalog:
react: ^19.0.0
react-dom: ^19.0.0
catalogs:
react18:
react: ^18.3.0
// packages/ui/package.json
{ "dependencies": { "react": "catalog:" } }
Source : pnpm — Catalogs
Plus de dérive de versions entre 30 paquets : on monte React une fois, dans le catalog. C’est l’équivalent natif de ce qu’on bricolait avant avec des resolutions ou des scripts de synchronisation.
6. Sécurité de la chaîne d’approvisionnement
L’année 2025 a été brutale pour npm. Les attaques Shai-Hulud (septembre puis novembre 2025) étaient des vers auto-réplicatifs : un script preinstall volait les jetons npm et GitHub du développeur, puis republiait des versions infectées de tous les paquets qu’il maintenait — propagation exponentielle, ~796 paquets compromis pour Shai-Hulud 2.0.
Sources : CISA — Widespread Supply Chain Compromise Impacting npm, Datadog Security Labs — Shai-Hulud 2.0
pnpm a réagi avec deux défenses désormais activées par défaut.
6.1 Scripts de build désactivés par défaut
Depuis pnpm 10, les scripts de cycle de vie (preinstall, postinstall, etc.) des dépendances ne s’exécutent plus automatiquement. Il faut explicitement autoriser un paquet à builder, via allowBuilds (anciennement onlyBuiltDependencies) :
# pnpm-workspace.yaml (pnpm 11)
allowBuilds:
- esbuild
- "@prisma/engines"
C’est exactement le vecteur qu’exploitait Shai-Hulud (le preinstall malveillant). npm exécute toujours ces scripts par défaut.
6.2 Délai minimal de publication (minimumReleaseAge)
pnpm peut refuser de résoudre une version publiée il y a moins de X minutes. Depuis pnpm 11, le défaut est 1 jour (1440 min).
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- my-internal-pkg # hotfix immédiat autorisé
Source : pnpm — Protecting our newsroom from npm supply chain attacks
Le raisonnement : un paquet malveillant est généralement retiré en quelques heures (les paquets compromis en septembre 2025 l’ont été en ~2,5 h). Recevoir ses dépendances avec un jour de retard est, pour la quasi-totalité des projets, un compromis indolore qui aurait bloqué les deux vagues Shai-Hulud.
Cette fonctionnalité existe aussi côté Bun (
minimumReleaseAge) et a été adoptée par l’écosystème comme bonne pratique. C’est pnpm qui l’a popularisée et activée par défaut.
7. Les concurrents en détail
7.1 npm
Le gestionnaire historique, livré avec Node. Sa force est d’être là, partout, sans rien installer. Sa stratégie reste le node_modules plat (avec ses fantômes et ses doublons), et il exécute les scripts de build par défaut. Il a rattrapé une partie du retard fonctionnel — workspaces, overrides, npm ci déterministe via package-lock.json — mais reste le plus lent et le moins économe en disque. C’est le choix par défaut « zéro friction », pas le choix optimisé.
7.2 Yarn — Classic (v1) vs Berry (v2+)
Il faut distinguer deux Yarn radicalement différents :
- Yarn Classic (v1) : même modèle plat que npm, en mode maintenance. À ne plus choisir pour un nouveau projet.
- Yarn Berry (v2/v3/v4) : réécriture complète, avec son innovation phare, Plug’n’Play (PnP).
PnP supprime carrément node_modules. À la place, un fichier .pnp.cjs à la racine mappe chaque import vers l’emplacement exact du paquet (souvent une archive .zip non décompressée dans .yarn/cache). Node est patché pour lire cette carte au lieu de parcourir des dossiers.
Avantages : installations quasi instantanées, zero-installs (le cache est commité dans le dépôt, clone = projet prêt), et une stricte interdiction des dépendances fantômes (un import non déclaré lève une erreur immédiate).
Inconvénient : c’est le modèle le plus disruptif. Tout outil qui suppose un vrai node_modules sur disque (certains bundlers, debuggers, frameworks comme React Native historiquement) nécessite un shim ou ne marche pas. Yarn Berry propose un nodeLinker: node-modules pour retomber sur un mode classique, ce qui annule une partie de l’intérêt.
Source : pnpm — Feature comparison
7.3 Bun
Bun n’est pas qu’un gestionnaire de paquets : c’est un runtime (alternative à Node, écrit en Zig) qui embarque un installeur, un bundler et un test runner. Pour la gestion de paquets, ses traits marquants :
- Vitesse brute inégalée à l’installation (cf. section 4).
- Lockfile texte :
bun.lock(lisible, diffable) est le défaut depuis Bun 1.2, remplaçant le binairebun.lockb. Source : Bun — Text-based lockfile - Linker isolé : historiquement Bun posait un
node_modulesplat. Depuis Bun 1.2, le isolated linker crée une structure stricte non hissée (façon pnpm), et c’est le défaut dans les workspaces pour les nouveaux projets (configVersion: 1). Source : Bun — Isolated installs - Store global partagé : depuis Bun 1.3.14,
--linker=isolatedpeut matérialiser les paquets une fois dans un dossier global et faire de chaquenode_modulesun symlink vers lui — convergence directe avec le modèle pnpm.
Bun est le choix « tout-en-un, performance maximale », au prix d’un écosystème plus jeune et d’une compatibilité Node encore en rattrapage sur certaines API.
8. Tableau comparatif récapitulatif
| Critère | npm | Yarn Classic | Yarn Berry (PnP) | pnpm | Bun |
|---|---|---|---|---|---|
Structure node_modules | Plat | Plat | Aucun (.pnp.cjs) | Symlinks + CAS | Plat ou isolé |
| Empêche les dép. fantômes | Non | Non | Oui | Oui | Oui (linker isolé) |
| Store partagé / dédup. disque | Non | Non | Cache zip | Oui (CAS, liens durs) | Oui (depuis 1.3.14) |
| Vitesse install (froid) | Lent | Moyen | Rapide | Rapide | Le plus rapide |
| Workspaces / monorepo | Oui | Oui | Oui | Oui (+ filtres, catalogs) | Oui |
| Scripts de build par défaut | Exécutés | Exécutés | Configurable | Désactivés | Configurable |
| Délai min. de publication | Non | Non | Non | Oui (1 j par défaut) | Oui |
| Runtime intégré | Non | Non | Non | Non | Oui |
| Zero-install | Non | Non | Oui | Non | Non |
Source de la matrice de fonctionnalités : pnpm — Feature comparison
9. pnpm 11 (avril 2026) : ce qui a changé
pnpm 11 est sorti le 28 avril 2026. Les évolutions notables :
- Store en SQLite : l’index du store, jadis des millions de petits fichiers JSON (un par paquet), est désormais une base SQLite unique. Moins de syscalls, installations plus rapides.
- Publication native : pnpm n’appelle plus le CLI npm en sous-main pour
publish,login,view,deprecate— implémentations natives. - Installs globaux isolés : chaque paquet installé globalement a son propre dossier (
package.json,node_modules, lockfile), fini les conflits entre outils globaux. - Défauts de sécurité durcis :
minimumReleaseAgeà 1 jour, blocage par défaut des sous-dépendances « exotiques ». - Séparation de la configuration :
.npmrcne sert plus qu’à l’authentification et au registre ; toute la config pnpm passe en YAML (pnpm-workspace.yaml,config.yaml). - Ruptures : Node.js 22+ requis, pnpm distribué en ESM pur,
onlyBuiltDependencies/neverBuiltDependenciesremplacés parallowBuilds, audit basé sur les identifiants GHSA (plus CVE), préfixe d’envpnpm_config_*.
Source : pnpm — Release 11.0
10. Conséquences du passage à pnpm en CI/CD
Migrer de npm à pnpm n’est pas transparent côté intégration continue : pnpm n’est pas livré par défaut là où npm l’est, et plusieurs commandes changent. Voici ce qui bouge concrètement.
10.1 Disponibilité dans les images Docker
Les images Docker officielles node embarquent npm nativement (et, historiquement, Yarn Classic), mais pas pnpm. La voie recommandée est Corepack, livré avec Node de la 14.19 jusqu’à (exclu) la 25.0 :
FROM node:24-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME/bin:$PATH"
RUN corepack enable # installe les shims pnpm/yarn sur le PATH
Source : Corepack, pnpm — Working with Docker
Corepack lit le champ packageManager du package.json et active la version exacte de pnpm épinglée par le projet — fini le « ça marche sur ma machine mais pas en CI » dû à un écart de version de gestionnaire. Deux nuances à connaître :
- Corepack reste marqué expérimental et opt-in (
corepack enableobligatoire). Il a été question de le retirer du bundle Node à partir de la v25 ; surveiller ce point lors des montées de version. - Si Corepack n’est pas disponible, le repli est
npm install -g pnpm@<version>— mais on perd l’épinglage par projet.
10.2 GitHub Actions
Le motif officiel combine pnpm/action-setup (installe pnpm) puis actions/setup-node avec cache: 'pnpm' (met en cache le store) — l’ordre compte, setup-node a besoin de pnpm déjà présent pour localiser le store :
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v6
with:
node-version: 24
cache: "pnpm" # met en cache ~/.pnpm-store via le lockfile
- run: pnpm install --frozen-lockfile
Source : pnpm — Continuous Integration
Cet exemple complète notre article GitHub Actions pour builder et déployer des images Docker : le même pipeline, mais avec pnpm en amont du build.
10.3 Les commandes qui changent
| Geste | npm | pnpm |
|---|---|---|
| Install reproductible (CI) | npm ci | pnpm install --frozen-lockfile |
| Exécution ponctuelle d’un binaire | npx <pkg> | pnpm dlx <pkg> |
| Lancer un script | npm run build | pnpm build (le run est optionnel) |
| Fichier de verrou | package-lock.json | pnpm-lock.yaml |
Bonne nouvelle : pnpm détecte l’environnement CI et bascule automatiquement en --frozen-lockfile (équivalent de npm ci : échec si le lockfile n’est pas à jour, jamais de modification silencieuse). L’expliciter reste préférable pour la lisibilité.
Source : pnpm — Continuous Integration
Deux pièges spécifiques au CI :
- pnpm 11 échoue sur un lockfile issu d’une version majeure plus récente. Si un développeur en local utilise pnpm 12 et que le CI est resté en 11, le build casse. D’où l’intérêt d’épingler la version partout (
packageManager+ Corepack). - Les scripts de build ne tournent pas par défaut (cf. section 6.1). Un paquet à compilation native (
esbuild,sharp,@prisma/engines) doit figurer dansallowBuilds, sinon l’artefact CI est incomplet — symptôme classique d’une migration npm→pnpm trop rapide.
10.4 Cache du store en Docker
Deux stratégies, selon que le runner supporte BuildKit ou non :
# Stratégie A — BuildKit : monter le store en cache (install complète, store réutilisé)
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile
# Stratégie B — sans cache mount : pnpm fetch pour le cache de couches
COPY pnpm-lock.yaml ./
RUN pnpm fetch --prod # ne dépend QUE du lockfile
COPY . .
RUN pnpm install --offline --prod # depuis le store déjà peuplé
Source : pnpm — Working with Docker
pnpm fetch est l’optimisation clé pour les VM éphémères (sans cache mount) : comme il ne lit que pnpm-lock.yaml, la couche n’est invalidée que lorsque les dépendances changent — le COPY . . du code source ne casse pas le cache d’installation. C’est l’équivalent pnpm de la séparation COPY package*.json / npm ci que l’on fait avec npm, en plus fin.
11. Quel gestionnaire choisir ?
Pour un nouveau projet ou un monorepo : pnpm. C’est le meilleur compromis correction / vitesse / disque / compatibilité, avec les défauts de sécurité les plus stricts de l’écosystème.
- npm : si vous voulez zéro dépendance externe et zéro réflexion, et que la performance ne compte pas. C’est déjà là.
- Yarn Classic : pour personne, sur un nouveau projet. Mode maintenance.
- Yarn Berry (PnP) : si vous voulez les zero-installs et que votre stack tolère l’absence de
node_modules. Modèle puissant mais exigeant en compatibilité outillage. - pnpm : le défaut raisonné. Strict, économe, excellent en monorepo, sûr par défaut.
- Bun : si vous adoptez le runtime Bun (ou cherchez la vitesse d’install maximale) et acceptez un écosystème plus jeune. Sa convergence récente vers le modèle pnpm (linker isolé, store global) montre que la direction prise par pnpm fait consensus.
L’installation d’un gestionnaire se fait proprement via Corepack (livré avec Node), qui épingle la version du gestionnaire par projet via le champ packageManager du package.json :
{ "packageManager": "pnpm@11.0.0" }
Source : Corepack
12. Pièges et limitations connues
Outils incompatibles avec les symlinks
La structure de pnpm repose sur des symlinks. Certains environnements ne les gèrent pas : React Native (Metro), certains bundlers serverless, le publish via bundledDependencies, ou Node lancé avec --preserve-symlinks. La parade est nodeLinker: hoisted, qui produit un node_modules plat à la npm — au prix de réintroduire les dépendances fantômes.
Source : pnpm — Settings (nodeLinker)
Le code qui dépendait des fantômes casse
Migrer un projet npm/Yarn vers pnpm révèle souvent des imports de paquets jamais déclarés (qui « marchaient » grâce au remontage). C’est un bug latent que pnpm met au jour : la correction est de déclarer ces dépendances dans package.json. Désagréable une fois, sain ensuite.
Scripts de build à autoriser explicitement
Depuis pnpm 10, un paquet qui a besoin de compiler à l’installation (esbuild, @prisma/engines, sharp…) ne le fera pas tant qu’il n’est pas dans allowBuilds. pnpm affiche un avertissement listant les paquets ignorés ; pnpm approve-builds aide à les ajouter interactivement. Surprenant la première fois, mais c’est précisément la défense anti-postinstall malveillant.
Ne pas mélanger les lockfiles
Un projet a un et un seul gestionnaire. Garder à la fois package-lock.json, yarn.lock et pnpm-lock.yaml dans le dépôt est une source de divergences silencieuses. Épingler le gestionnaire via packageManager + Corepack évite qu’un collègue installe avec le mauvais outil.
Conclusion
Les quatre outils convergent lentement vers le même constat que pnpm a posé en premier : le node_modules plat était une erreur, la déduplication par store partagé est la bonne réponse au disque, et la chaîne d’approvisionnement npm ne mérite plus une confiance aveugle. Yarn Berry y est arrivé par PnP, Bun par son linker isolé et son store global, pnpm par les liens durs et les symlinks.
pnpm reste, en 2026, celui qui assemble ces idées avec le meilleur équilibre : assez strict pour empêcher les bugs de résolution, assez compatible pour ne pas casser l’outillage, et avec les défauts de sécurité les plus défensifs du lot. Pour un nouveau projet, c’est le choix par défaut qui demande le moins de justifications.