
Surveillance des changements dans le système de fichiers (fs.watch)
Apprenez a utiliser fs.watch pour detecter les modifications, renommages, creations ou suppressions de fichiers et repertoires en Node.js, avec ses avantages et limites.
Reagir aux modifications : le besoin de surveillance
Dans de nombreuses applications, il est utile, voire nécessaire, de réagir automatiquement lorsque des fichiers ou des répertoires sont modifiés sur le système de fichiers. Imaginez un environnement de développement où le serveur doit redémarrer automatiquement lorsqu'un fichier source est modifié (hot-reloading), une application qui doit recharger sa configuration si le fichier de configuration change, ou un service qui doit traiter des fichiers dès qu'ils sont déposés dans un répertoire d'upload.
Plutôt que de vérifier périodiquement l'état des fichiers (une technique appelée "polling", gourmande en ressources et peu réactive), Node.js propose une approche plus efficace basée sur les événements système : la fonction `fs.watch()`. Elle permet de s'abonner aux notifications du système d'exploitation concernant les changements sur un fichier ou un répertoire spécifique.
Il est important de noter que `fs.watch()` est généralement plus efficace et fiable que l'ancienne API `fs.watchFile()` (qui utilise le polling). Cependant, `fs.watch()` elle-même présente des comportements qui peuvent varier selon le système d'exploitation, ce qui demande une certaine prudence lors de son utilisation.
Utilisation de `fs.watch`
La fonction `fs.watch()` crée un objet `fs.FSWatcher` qui émet des événements lorsqu'il détecte des changements sur le chemin surveillé.
Sa signature de base est :
fs.watch(filename, [options], listener)filename: Le chemin complet du fichier ou du répertoire à surveiller.options(optionnel) : Un objet ou une chaîne pour spécifier des options :persistent(booléen) : Si `true` (par défaut), le processus Node.js continuera de s'exécuter tant que le watcher est actif. Si `false`, le programme peut se terminer même si le watcher est actif.recursive(booléen) : Si `true`, la surveillance s'étend aux sous-répertoires. Cette option n'est pas supportée sur toutes les plateformes (fonctionne généralement bien sur macOS et Windows, moins sur Linux sans configuration spécifique). Par défaut `false`.encoding(chaîne) : L'encodage à utiliser pour le nom de fichier passé au `listener` (par défaut `'utf8'`).
listener: Une fonction de callback qui sera appelée lorsqu'un événement de changement est détecté. Elle reçoit deux arguments : `(eventType, filename)`.
Le `listener` est au coeur de la surveillance :
eventType: Une chaîne indiquant le type d'événement détecté. Les valeurs les plus courantes sont `'rename'` et `'change'`. Attention : la signification exacte est dépendante du système d'exploitation. `'rename'` peut indiquer une création, une suppression, ou un renommage effectif. `'change'` indique généralement une modification du contenu. Il n'est pas garanti que `eventType` soit toujours fourni ou exact.filename: Le nom du fichier ou du répertoire dans le dossier surveillé qui a déclenché l'événement. Attention : Ce paramètre n'est pas toujours fourni (peut être `null`) et sa disponibilité dépend de la plateforme et du type d'événement, notamment lors de la surveillance récursive de répertoires.
L'objet `fs.FSWatcher` retourné par `fs.watch()` émet également un événement `'error'` en cas de problème et possède une méthode essentielle : `watcher.close()`, qui doit être appelée pour arrêter la surveillance et libérer les ressources système associées.
Exemple pratique de surveillance
Voici un exemple de surveillance d'un fichier spécifique (`config.json`) et d'un répertoire (`watched_dir`) :
const fs = require('fs');
const fileToWatch = 'config.json';
const dirToWatch = 'watched_dir';
// S'assurer que le répertoire existe (création silencieuse si besoin)
try { fs.mkdirSync(dirToWatch); } catch(e) { if (e.code !== 'EEXIST') throw e; }
// Créer un fichier initial si besoin
try { fs.writeFileSync(fileToWatch, '{}', { flag: 'wx' }); } catch(e) { if (e.code !== 'EEXIST') throw e; }
console.log(`Surveillance des changements sur '${fileToWatch}'...`);
const fileWatcher = fs.watch(fileToWatch, (eventType, filename) => {
// filename est souvent null lors de la surveillance d'un fichier unique
console.log(`Evénement sur ${fileToWatch}: ${eventType}`);
if (eventType === 'change') {
console.log('Le contenu du fichier a probablement changé. Recharger la configuration ?');
// Ici, on pourrait relire le fichier
} else if (eventType === 'rename') {
console.log('Le fichier a peut-être été renommé, supprimé ou recréé.');
// Gérer la disparition/recréation potentielle
}
});
fileWatcher.on('error', (error) => {
console.error(`Erreur du watcher de fichier (${fileToWatch}):`, error);
});
console.log(`Surveillance des changements dans '${dirToWatch}'...`);
const dirWatcher = fs.watch(dirToWatch, { recursive: false }, (eventType, filename) => {
if (filename) {
console.log(`Evénement dans ${dirToWatch}: ${eventType} sur le fichier/répertoire: ${filename}`);
// Agir en fonction de l'événement et du nom de fichier
// Exemple: traiter un nouveau fichier déposé
} else {
console.log(`Evénement dans ${dirToWatch}: ${eventType} (nom de fichier non fourni)`);
}
});
dirWatcher.on('error', (error) => {
console.error(`Erreur du watcher de répertoire (${dirToWatch}):`, error);
});
// Pour arrêter la surveillance après un certain temps (ou sur un signal)
setTimeout(() => {
console.log('Arrêt de la surveillance...');
fileWatcher.close();
dirWatcher.close();
console.log('Watchers arrêtés.');
}, 30000); // Arrête après 30 secondes
console.log('Modifiez le fichier config.json ou ajoutez/supprimez des fichiers dans watched_dir pour voir les événements.');
Limitations, pieges et bonnes pratiques
Fiabilité des événements : C'est le point le plus critique. Ne vous fiez pas aveuglément aux valeurs de `eventType` et `filename`. Un simple enregistrement de fichier dans un éditeur peut déclencher plusieurs événements (`rename`, puis `change`, ou juste `rename`). La suppression peut être un `rename`. Le `filename` peut manquer. Votre logique doit être robuste face à ces incertitudes. Souvent, il est plus sûr de simplement réagir au fait qu'un événement s'est produit et de revérifier l'état du fichier/répertoire si nécessaire.
Evénements multiples (Debouncing/Throttling) : Une seule action utilisateur (comme enregistrer un fichier) peut générer une rafale d'événements `fs.watch`. Pour éviter de réagir excessivement (par exemple, redémarrer un serveur 5 fois de suite), il est quasi indispensable d'utiliser des techniques de debouncing (attendre une pause dans les événements avant de réagir) ou de throttling (ne réagir qu'une fois toutes les X millisecondes). Des bibliothèques comme `lodash.debounce` peuvent aider.
Gestion des erreurs : Toujours écouter l'événement `'error'` sur l'objet `FSWatcher` retourné.
Fermeture du watcher : N'oubliez pas d'appeler `watcher.close()` lorsque vous n'avez plus besoin de surveiller, pour libérer les ressources système (descripteurs de fichiers, etc.). C'est crucial pour éviter les fuites.
Alternative robuste : `chokidar`
En raison des incohérences multiplateformes et des limitations de `fs.watch` natif, la communauté Node.js s'est largement tournée vers la bibliothèque `chokidar`. Elle fournit une API de surveillance de fichiers beaucoup plus stable, cohérente et riche en fonctionnalités, tout en utilisant `fs.watch` (et d'autres mécanismes) en interne lorsque c'est approprié. `chokidar` gère pour vous de nombreux pièges, offre des événements plus précis (`add`, `addDir`, `change`, `unlink`, `unlinkDir`), gère mieux la récursivité et la disponibilité des noms de fichiers. Pour toute application de production nécessitant une surveillance fiable des fichiers, l'utilisation de `chokidar` est fortement recommandée.
// Exemple très basique avec chokidar (nécessite npm install chokidar)
const chokidar = require('chokidar');
const watcher = chokidar.watch('watched_dir', {
ignored: /(^|\/)\../, // ignore les fichiers dotfiles
persistent: true,
ignoreInitial: true // Ne pas déclencher d'événements pour les fichiers existants au démarrage
});
console.log('Surveillance avec Chokidar...');
watcher
.on('add', path => console.log(`Chokidar: Fichier ajouté: ${path}`))
.on('change', path => console.log(`Chokidar: Fichier modifié: ${path}`))
.on('unlink', path => console.log(`Chokidar: Fichier supprimé: ${path}`))
.on('error', error => console.error(`Chokidar: Erreur du watcher: ${error}`))
.on('ready', () => console.log('Chokidar: Prêt pour les changements.'));
// Pour arrêter :
// setTimeout(() => watcher.close(), 30000);
Conclusion : un outil utile mais a manier avec soin
`fs.watch()` est l'outil natif de Node.js pour la surveillance des changements du système de fichiers, offrant une alternative efficace au polling. Il est utile pour des tâches simples ou lorsque la dépendance externe n'est pas souhaitée.
Cependant, ses limitations et ses incohérences multiplateformes nécessitent une approche prudente et souvent l'implémentation de logiques supplémentaires comme le debouncing. Pour des besoins plus complexes ou pour une fiabilité accrue en production, l'utilisation de bibliothèques éprouvées comme `chokidar` est la solution privilégiée par la communauté.