| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const fs = require("fs");const path = require("path");const { EventEmitter } = require("events");const reducePlan = require("./reducePlan");const IS_OSX = require("os").platform() === "darwin";const IS_WIN = require("os").platform() === "win32";const SUPPORTS_RECURSIVE_WATCHING = IS_OSX || IS_WIN;// Use 20 for OSX to make `FSWatcher.close` faster// https://github.com/nodejs/node/issues/29949const watcherLimit =	+process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 20 : 10000);const recursiveWatcherLogging = !!process.env	.WATCHPACK_RECURSIVE_WATCHER_LOGGING;let isBatch = false;let watcherCount = 0;/** @type {Map<Watcher, string>} */const pendingWatchers = new Map();/** @type {Map<string, RecursiveWatcher>} */const recursiveWatchers = new Map();/** @type {Map<string, DirectWatcher>} */const directWatchers = new Map();/** @type {Map<Watcher, RecursiveWatcher | DirectWatcher>} */const underlyingWatcher = new Map();function createEPERMError(filePath) {	const error = new Error(`Operation not permitted: ${filePath}`);	error.code = "EPERM";	return error;}function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {	return (type, filename) => {		// TODO: After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event.		// Here we just ignore it and keep the same behavior as before v22		// https://github.com/libuv/libuv/pull/4376		if (			type === "rename" &&			path.isAbsolute(filename) &&			path.basename(filename) === path.basename(filePath)		) {			if (!IS_OSX) {				// Before v22, windows will throw EPERM error				watcher.emit("error", createEPERMError(filename));			}			// Before v22, macos nothing to do			return;		}		handleChangeEvent(type, filename);	};}class DirectWatcher {	constructor(filePath) {		this.filePath = filePath;		this.watchers = new Set();		this.watcher = undefined;		try {			const watcher = fs.watch(filePath);			this.watcher = watcher;			const handleChangeEvent = createHandleChangeEvent(				watcher,				filePath,				(type, filename) => {					for (const w of this.watchers) {						w.emit("change", type, filename);					}				}			);			watcher.on("change", handleChangeEvent);			watcher.on("error", error => {				for (const w of this.watchers) {					w.emit("error", error);				}			});		} catch (err) {			process.nextTick(() => {				for (const w of this.watchers) {					w.emit("error", err);				}			});		}		watcherCount++;	}	add(watcher) {		underlyingWatcher.set(watcher, this);		this.watchers.add(watcher);	}	remove(watcher) {		this.watchers.delete(watcher);		if (this.watchers.size === 0) {			directWatchers.delete(this.filePath);			watcherCount--;			if (this.watcher) this.watcher.close();		}	}	getWatchers() {		return this.watchers;	}}class RecursiveWatcher {	constructor(rootPath) {		this.rootPath = rootPath;		/** @type {Map<Watcher, string>} */		this.mapWatcherToPath = new Map();		/** @type {Map<string, Set<Watcher>>} */		this.mapPathToWatchers = new Map();		this.watcher = undefined;		try {			const watcher = fs.watch(rootPath, {				recursive: true			});			this.watcher = watcher;			watcher.on("change", (type, filename) => {				if (!filename) {					if (recursiveWatcherLogging) {						process.stderr.write(							`[watchpack] dispatch ${type} event in recursive watcher (${this.rootPath}) to all watchers\n`						);					}					for (const w of this.mapWatcherToPath.keys()) {						w.emit("change", type);					}				} else {					const dir = path.dirname(filename);					const watchers = this.mapPathToWatchers.get(dir);					if (recursiveWatcherLogging) {						process.stderr.write(							`[watchpack] dispatch ${type} event in recursive watcher (${								this.rootPath							}) for '${filename}' to ${								watchers ? watchers.size : 0							} watchers\n`						);					}					if (watchers === undefined) return;					for (const w of watchers) {						w.emit("change", type, path.basename(filename));					}				}			});			watcher.on("error", error => {				for (const w of this.mapWatcherToPath.keys()) {					w.emit("error", error);				}			});		} catch (err) {			process.nextTick(() => {				for (const w of this.mapWatcherToPath.keys()) {					w.emit("error", err);				}			});		}		watcherCount++;		if (recursiveWatcherLogging) {			process.stderr.write(				`[watchpack] created recursive watcher at ${rootPath}\n`			);		}	}	add(filePath, watcher) {		underlyingWatcher.set(watcher, this);		const subpath = filePath.slice(this.rootPath.length + 1) || ".";		this.mapWatcherToPath.set(watcher, subpath);		const set = this.mapPathToWatchers.get(subpath);		if (set === undefined) {			const newSet = new Set();			newSet.add(watcher);			this.mapPathToWatchers.set(subpath, newSet);		} else {			set.add(watcher);		}	}	remove(watcher) {		const subpath = this.mapWatcherToPath.get(watcher);		if (!subpath) return;		this.mapWatcherToPath.delete(watcher);		const set = this.mapPathToWatchers.get(subpath);		set.delete(watcher);		if (set.size === 0) {			this.mapPathToWatchers.delete(subpath);		}		if (this.mapWatcherToPath.size === 0) {			recursiveWatchers.delete(this.rootPath);			watcherCount--;			if (this.watcher) this.watcher.close();			if (recursiveWatcherLogging) {				process.stderr.write(					`[watchpack] closed recursive watcher at ${this.rootPath}\n`				);			}		}	}	getWatchers() {		return this.mapWatcherToPath;	}}class Watcher extends EventEmitter {	close() {		if (pendingWatchers.has(this)) {			pendingWatchers.delete(this);			return;		}		const watcher = underlyingWatcher.get(this);		watcher.remove(this);		underlyingWatcher.delete(this);	}}const createDirectWatcher = filePath => {	const existing = directWatchers.get(filePath);	if (existing !== undefined) return existing;	const w = new DirectWatcher(filePath);	directWatchers.set(filePath, w);	return w;};const createRecursiveWatcher = rootPath => {	const existing = recursiveWatchers.get(rootPath);	if (existing !== undefined) return existing;	const w = new RecursiveWatcher(rootPath);	recursiveWatchers.set(rootPath, w);	return w;};const execute = () => {	/** @type {Map<string, Watcher[] | Watcher>} */	const map = new Map();	const addWatcher = (watcher, filePath) => {		const entry = map.get(filePath);		if (entry === undefined) {			map.set(filePath, watcher);		} else if (Array.isArray(entry)) {			entry.push(watcher);		} else {			map.set(filePath, [entry, watcher]);		}	};	for (const [watcher, filePath] of pendingWatchers) {		addWatcher(watcher, filePath);	}	pendingWatchers.clear();	// Fast case when we are not reaching the limit	if (!SUPPORTS_RECURSIVE_WATCHING || watcherLimit - watcherCount >= map.size) {		// Create watchers for all entries in the map		for (const [filePath, entry] of map) {			const w = createDirectWatcher(filePath);			if (Array.isArray(entry)) {				for (const item of entry) w.add(item);			} else {				w.add(entry);			}		}		return;	}	// Reconsider existing watchers to improving watch plan	for (const watcher of recursiveWatchers.values()) {		for (const [w, subpath] of watcher.getWatchers()) {			addWatcher(w, path.join(watcher.rootPath, subpath));		}	}	for (const watcher of directWatchers.values()) {		for (const w of watcher.getWatchers()) {			addWatcher(w, watcher.filePath);		}	}	// Merge map entries to keep watcher limit	// Create a 10% buffer to be able to enter fast case more often	const plan = reducePlan(map, watcherLimit * 0.9);	// Update watchers for all entries in the map	for (const [filePath, entry] of plan) {		if (entry.size === 1) {			for (const [watcher, filePath] of entry) {				const w = createDirectWatcher(filePath);				const old = underlyingWatcher.get(watcher);				if (old === w) continue;				w.add(watcher);				if (old !== undefined) old.remove(watcher);			}		} else {			const filePaths = new Set(entry.values());			if (filePaths.size > 1) {				const w = createRecursiveWatcher(filePath);				for (const [watcher, watcherPath] of entry) {					const old = underlyingWatcher.get(watcher);					if (old === w) continue;					w.add(watcherPath, watcher);					if (old !== undefined) old.remove(watcher);				}			} else {				for (const filePath of filePaths) {					const w = createDirectWatcher(filePath);					for (const watcher of entry.keys()) {						const old = underlyingWatcher.get(watcher);						if (old === w) continue;						w.add(watcher);						if (old !== undefined) old.remove(watcher);					}				}			}		}	}};exports.watch = filePath => {	const watcher = new Watcher();	// Find an existing watcher	const directWatcher = directWatchers.get(filePath);	if (directWatcher !== undefined) {		directWatcher.add(watcher);		return watcher;	}	let current = filePath;	for (;;) {		const recursiveWatcher = recursiveWatchers.get(current);		if (recursiveWatcher !== undefined) {			recursiveWatcher.add(filePath, watcher);			return watcher;		}		const parent = path.dirname(current);		if (parent === current) break;		current = parent;	}	// Queue up watcher for creation	pendingWatchers.set(watcher, filePath);	if (!isBatch) execute();	return watcher;};exports.batch = fn => {	isBatch = true;	try {		fn();	} finally {		isBatch = false;		execute();	}};exports.getNumberOfWatchers = () => {	return watcherCount;};exports.createHandleChangeEvent = createHandleChangeEvent;exports.watcherLimit = watcherLimit;
 |