Comment modifier un module tiers (vendor ou node_module)
Publié le par Alexandre Normand révisé le 17/04/2026
Quand une dépendance tierce contient un bug, la tentation est de modifier directement le fichier dans vendor/ ou node_modules/. C'est rapide, mais le correctif disparaît au prochain composer install ou npm install. L'une des bonnes approches est d'appliquer un patch versionné, rejoué automatiquement à chaque installation.
C'est quoi un patch (Le principe) ?
Un patch est un fichier texte au format diff qui décrit exactement quelles lignes doivent être ajoutées ou supprimées dans un fichier source. Les outils composer-patches (PHP) et patch-package (Node) appliquent ces patches automatiquement après chaque installation des dépendances.
Le patch est commité dans ton dépôt Git. La dépendance reste intacte dans son code source officiel. Tout le monde dans l'équipe bénéficie du correctif sans action manuelle.
PHP / Composer : cweagans/composer-patches
Installation
composer require "cweagans/composer-patches:^1.7"
Attention : la v2 du plugin change radicalement la configuration. Je préfère la v1.7 qui est stable et largement documentée, facile avec une IA.
Créer le patch
Commence par identifier les lignes à corriger dans le fichier vendor. Puis crée un fichier .patch dans un dossier patches/ à la racine du projet.
Exemple avec Laravel
imaginons un bug dans le package spatie/laravel-permission où une méthode ne gère pas correctement un cas limite.
Fichier patches/spatie_laravel_permission_fix.patch :
--- a/src/Traits/HasRoles.php
+++ b/src/Traits/HasRoles.php
@@ -45,7 +45,7 @@
*/
public function assignRole(...$roles)
{
- $roles = collect($roles)->flatten()->map(function ($role) {
+ $roles = collect($roles)->flatten()->filter()->map(function ($role) {
if (empty($role)) {
return false;
}
Déclarer le patch dans composer.json
Cette fois, c'est au format JSON.
"extra": {
"patches": {
"spatie/laravel-permission": {
"Fix empty role handling in assignRole": "patches/spatie_laravel_permission_fix.patch"
}
}
}
L'appliquer
composer install
Le plugin affiche dans la sortie :
- Applying patches for spatie/laravel-permission
patches/spatie_laravel_permission_fix.patch (Fix empty role handling in assignRole)
Le patch est rejoué à chaque composer install et composer update. Il est versionné avec le projet.
Générer le patch proprement
La façon la plus fiable de générer un patch est de copier le fichier original, modifier la copie, puis faire un diff :
cp vendor/spatie/laravel-permission/src/Traits/HasRoles.php /tmp/HasRoles_patched.php
# modifier /tmp/HasRoles_patched.php
diff -u vendor/spatie/laravel-permission/src/Traits/HasRoles.php /tmp/HasRoles_patched.php > patches/spatie_laravel_permission_fix.patch
Node.js / npm : patch-package
Installation
npm install patch-package --save-dev
Ajoute le script postinstall dans ton package.json pour que les patches soient appliqués automatiquement :
"scripts": {
"postinstall": "patch-package"
}
Modifier la dépendance et générer le patch
Contrairement à composer-patches, patch-package génère le fichier .patch à partir des modifications que tu as déjà faites directement dans node_modules/.
Exemple avec Nuxt.js
imaginons un bug dans le module @nuxtjs/i18n où la détection de langue ne fonctionne pas correctement sur certaines routes.
- Modifie directement le fichier dans node_modules/ :
# édite node_modules/@nuxtjs/i18n/dist/runtime/composables/route-locale.js
- Une fois la modification faite, génère le patch :
npx patch-package @nuxtjs/i18n
Cela crée automatiquement le fichier patches/@nuxtjs+i18n+8.3.1.patch (le numéro de version est inclus dans le nom).
Le fichier généré ressemble à :
diff --git a/node_modules/@nuxtjs/i18n/dist/runtime/composables/route-locale.js b/node_modules/@nuxtjs/i18n/dist/runtime/composables/route-locale.js
index abc1234..def5678 100644
--- a/node_modules/@nuxtjs/i18n/dist/runtime/composables/route-locale.js
+++ b/node_modules/@nuxtjs/i18n/dist/runtime/composables/route-locale.js
@@ -12,7 +12,7 @@
export function detectLocaleFromRoute(route) {
- const locale = route.params.locale
+ const locale = route.params.locale ?? route.query.lang
if (!locale) {
return defaultLocale
}
Appliquer au prochain install, avec la commande npm install. Le script postinstall déclenche automatiquement patch-package, qui applique tous les patches présents dans le dossier patches/.
Bonnes pratiques communes
Commite toujours le dossier patches/ dans ton dépôt Git. C'est lui qui porte le correctif.
Documente chaque patch, garde une trace dans ton README ou dans un fichier patches/README.md expliquant pourquoi chaque patch existe, quelle version est concernée, et si un ticket a été ouvert.
Supprime le patch dès que possible, surveille les releases de la dépendance. Dès que le correctif est intégré officiellement, supprime le patch et mets à jour la dépendance.
Teste après chaque mise à jour de la dépendance. Si tu mets à jour la version d'un package patché, le patch peut échouer à s'appliquer si le code a changé autour des lignes modifiées. Vérifie les logs d'installation.
Alternative au patch
PHP / Composer
- Surcharge de classe : étendre la classe du vendor dans ton propre code et remplacer la méthode problématique (fonctionne si le package le permet via l'autoloading)
- Fork du package : forker le repo, appliquer le correctif, et pointer ton composer.json vers ton fork via le champ repositories
- Hook / Event : si le package expose des events ou des hooks, brancher sa propre logique dessus sans toucher au code source
Node / npm
- Fork + résolution dans package.json : même logique que Composer, pointer vers un fork GitHub avec le champ resolutions (Yarn) ou overrides (npm v8+)
- Alias de package : remplacer un package par un autre compatible via npm install mon-fork@npm:package-original
- Module augmentation TypeScript : pour les projets TS, étendre les types et wrapper le module sans le modifier
Dans tous les cas
Nous devrions ouvrir une PR ou MR et attendre le merge (la vraie solution long terme). Mais ça peut prendre du temps, voir le jamais être appliqué.
Envisager de changer de package pour un alternatif qui ne présente pas le bug.
J'ai une préférence par l'option de surcharge en PHP. Mais il faut être dans un environnement le permettant et que le package soit bien conçu avec de l'héritage. Le patch est surtout utile quand il n'y a pas de point d'extension prévu.
Résumé
PHP / Composer
- Outil : cweagans/composer-patches ^1.7
- Génération : diff -u original modifié > patches/fix.patch
- Configuration : composer.json > extra.patches
- Application : composer install
Node / npm
- Outil : patch-package
- Génération : npx patch-package nom-du-package
- Configuration : package.json > scripts.postinstall
- Application : npm install
Les deux approches reposent sur le même mécanisme : un fichier diff versionné, appliqué automatiquement. C'est propre, traçable, et ça survit aux réinstallations.