diff options
Diffstat (limited to 'node_modules/watchpack/lib/watchpack.js')
| -rw-r--r-- | node_modules/watchpack/lib/watchpack.js | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/node_modules/watchpack/lib/watchpack.js b/node_modules/watchpack/lib/watchpack.js new file mode 100644 index 00000000..ddc9138c --- /dev/null +++ b/node_modules/watchpack/lib/watchpack.js @@ -0,0 +1,383 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const getWatcherManager = require("./getWatcherManager"); +const LinkResolver = require("./LinkResolver"); +const EventEmitter = require("events").EventEmitter; +const globToRegExp = require("glob-to-regexp"); +const watchEventSource = require("./watchEventSource"); + +const EMPTY_ARRAY = []; +const EMPTY_OPTIONS = {}; + +function addWatchersToSet(watchers, set) { + for (const ww of watchers) { + const w = ww.watcher; + if (!set.has(w.directoryWatcher)) { + set.add(w.directoryWatcher); + } + } +} + +const stringToRegexp = ignored => { + const source = globToRegExp(ignored, { globstar: true, extended: true }) + .source; + const matchingStart = source.slice(0, source.length - 1) + "(?:$|\\/)"; + return matchingStart; +}; + +const ignoredToFunction = ignored => { + if (Array.isArray(ignored)) { + const regexp = new RegExp(ignored.map(i => stringToRegexp(i)).join("|")); + return x => regexp.test(x.replace(/\\/g, "/")); + } else if (typeof ignored === "string") { + const regexp = new RegExp(stringToRegexp(ignored)); + return x => regexp.test(x.replace(/\\/g, "/")); + } else if (ignored instanceof RegExp) { + return x => ignored.test(x.replace(/\\/g, "/")); + } else if (ignored instanceof Function) { + return ignored; + } else if (ignored) { + throw new Error(`Invalid option for 'ignored': ${ignored}`); + } else { + return () => false; + } +}; + +const normalizeOptions = options => { + return { + followSymlinks: !!options.followSymlinks, + ignored: ignoredToFunction(options.ignored), + poll: options.poll + }; +}; + +const normalizeCache = new WeakMap(); +const cachedNormalizeOptions = options => { + const cacheEntry = normalizeCache.get(options); + if (cacheEntry !== undefined) return cacheEntry; + const normalized = normalizeOptions(options); + normalizeCache.set(options, normalized); + return normalized; +}; + +class WatchpackFileWatcher { + constructor(watchpack, watcher, files) { + this.files = Array.isArray(files) ? files : [files]; + this.watcher = watcher; + watcher.on("initial-missing", type => { + for (const file of this.files) { + if (!watchpack._missing.has(file)) + watchpack._onRemove(file, file, type); + } + }); + watcher.on("change", (mtime, type) => { + for (const file of this.files) { + watchpack._onChange(file, mtime, file, type); + } + }); + watcher.on("remove", type => { + for (const file of this.files) { + watchpack._onRemove(file, file, type); + } + }); + } + + update(files) { + if (!Array.isArray(files)) { + if (this.files.length !== 1) { + this.files = [files]; + } else if (this.files[0] !== files) { + this.files[0] = files; + } + } else { + this.files = files; + } + } + + close() { + this.watcher.close(); + } +} + +class WatchpackDirectoryWatcher { + constructor(watchpack, watcher, directories) { + this.directories = Array.isArray(directories) ? directories : [directories]; + this.watcher = watcher; + watcher.on("initial-missing", type => { + for (const item of this.directories) { + watchpack._onRemove(item, item, type); + } + }); + watcher.on("change", (file, mtime, type) => { + for (const item of this.directories) { + watchpack._onChange(item, mtime, file, type); + } + }); + watcher.on("remove", type => { + for (const item of this.directories) { + watchpack._onRemove(item, item, type); + } + }); + } + + update(directories) { + if (!Array.isArray(directories)) { + if (this.directories.length !== 1) { + this.directories = [directories]; + } else if (this.directories[0] !== directories) { + this.directories[0] = directories; + } + } else { + this.directories = directories; + } + } + + close() { + this.watcher.close(); + } +} + +class Watchpack extends EventEmitter { + constructor(options) { + super(); + if (!options) options = EMPTY_OPTIONS; + this.options = options; + this.aggregateTimeout = + typeof options.aggregateTimeout === "number" + ? options.aggregateTimeout + : 200; + this.watcherOptions = cachedNormalizeOptions(options); + this.watcherManager = getWatcherManager(this.watcherOptions); + this.fileWatchers = new Map(); + this.directoryWatchers = new Map(); + this._missing = new Set(); + this.startTime = undefined; + this.paused = false; + this.aggregatedChanges = new Set(); + this.aggregatedRemovals = new Set(); + this.aggregateTimer = undefined; + this._onTimeout = this._onTimeout.bind(this); + } + + watch(arg1, arg2, arg3) { + let files, directories, missing, startTime; + if (!arg2) { + ({ + files = EMPTY_ARRAY, + directories = EMPTY_ARRAY, + missing = EMPTY_ARRAY, + startTime + } = arg1); + } else { + files = arg1; + directories = arg2; + missing = EMPTY_ARRAY; + startTime = arg3; + } + this.paused = false; + const fileWatchers = this.fileWatchers; + const directoryWatchers = this.directoryWatchers; + const ignored = this.watcherOptions.ignored; + const filter = path => !ignored(path); + const addToMap = (map, key, item) => { + const list = map.get(key); + if (list === undefined) { + map.set(key, item); + } else if (Array.isArray(list)) { + list.push(item); + } else { + map.set(key, [list, item]); + } + }; + const fileWatchersNeeded = new Map(); + const directoryWatchersNeeded = new Map(); + const missingFiles = new Set(); + if (this.watcherOptions.followSymlinks) { + const resolver = new LinkResolver(); + for (const file of files) { + if (filter(file)) { + for (const innerFile of resolver.resolve(file)) { + if (file === innerFile || filter(innerFile)) { + addToMap(fileWatchersNeeded, innerFile, file); + } + } + } + } + for (const file of missing) { + if (filter(file)) { + for (const innerFile of resolver.resolve(file)) { + if (file === innerFile || filter(innerFile)) { + missingFiles.add(file); + addToMap(fileWatchersNeeded, innerFile, file); + } + } + } + } + for (const dir of directories) { + if (filter(dir)) { + let first = true; + for (const innerItem of resolver.resolve(dir)) { + if (filter(innerItem)) { + addToMap( + first ? directoryWatchersNeeded : fileWatchersNeeded, + innerItem, + dir + ); + } + first = false; + } + } + } + } else { + for (const file of files) { + if (filter(file)) { + addToMap(fileWatchersNeeded, file, file); + } + } + for (const file of missing) { + if (filter(file)) { + missingFiles.add(file); + addToMap(fileWatchersNeeded, file, file); + } + } + for (const dir of directories) { + if (filter(dir)) { + addToMap(directoryWatchersNeeded, dir, dir); + } + } + } + // Close unneeded old watchers + // and update existing watchers + for (const [key, w] of fileWatchers) { + const needed = fileWatchersNeeded.get(key); + if (needed === undefined) { + w.close(); + fileWatchers.delete(key); + } else { + w.update(needed); + fileWatchersNeeded.delete(key); + } + } + for (const [key, w] of directoryWatchers) { + const needed = directoryWatchersNeeded.get(key); + if (needed === undefined) { + w.close(); + directoryWatchers.delete(key); + } else { + w.update(needed); + directoryWatchersNeeded.delete(key); + } + } + // Create new watchers and install handlers on these watchers + watchEventSource.batch(() => { + for (const [key, files] of fileWatchersNeeded) { + const watcher = this.watcherManager.watchFile(key, startTime); + if (watcher) { + fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files)); + } + } + for (const [key, directories] of directoryWatchersNeeded) { + const watcher = this.watcherManager.watchDirectory(key, startTime); + if (watcher) { + directoryWatchers.set( + key, + new WatchpackDirectoryWatcher(this, watcher, directories) + ); + } + } + }); + this._missing = missingFiles; + this.startTime = startTime; + } + + close() { + this.paused = true; + if (this.aggregateTimer) clearTimeout(this.aggregateTimer); + for (const w of this.fileWatchers.values()) w.close(); + for (const w of this.directoryWatchers.values()) w.close(); + this.fileWatchers.clear(); + this.directoryWatchers.clear(); + } + + pause() { + this.paused = true; + if (this.aggregateTimer) clearTimeout(this.aggregateTimer); + } + + getTimes() { + const directoryWatchers = new Set(); + addWatchersToSet(this.fileWatchers.values(), directoryWatchers); + addWatchersToSet(this.directoryWatchers.values(), directoryWatchers); + const obj = Object.create(null); + for (const w of directoryWatchers) { + const times = w.getTimes(); + for (const file of Object.keys(times)) obj[file] = times[file]; + } + return obj; + } + + getTimeInfoEntries() { + const map = new Map(); + this.collectTimeInfoEntries(map, map); + return map; + } + + collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { + const allWatchers = new Set(); + addWatchersToSet(this.fileWatchers.values(), allWatchers); + addWatchersToSet(this.directoryWatchers.values(), allWatchers); + const safeTime = { value: 0 }; + for (const w of allWatchers) { + w.collectTimeInfoEntries(fileTimestamps, directoryTimestamps, safeTime); + } + } + + getAggregated() { + if (this.aggregateTimer) { + clearTimeout(this.aggregateTimer); + this.aggregateTimer = undefined; + } + const changes = this.aggregatedChanges; + const removals = this.aggregatedRemovals; + this.aggregatedChanges = new Set(); + this.aggregatedRemovals = new Set(); + return { changes, removals }; + } + + _onChange(item, mtime, file, type) { + file = file || item; + if (!this.paused) { + this.emit("change", file, mtime, type); + if (this.aggregateTimer) clearTimeout(this.aggregateTimer); + this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); + } + this.aggregatedRemovals.delete(item); + this.aggregatedChanges.add(item); + } + + _onRemove(item, file, type) { + file = file || item; + if (!this.paused) { + this.emit("remove", file, type); + if (this.aggregateTimer) clearTimeout(this.aggregateTimer); + this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); + } + this.aggregatedChanges.delete(item); + this.aggregatedRemovals.add(item); + } + + _onTimeout() { + this.aggregateTimer = undefined; + const changes = this.aggregatedChanges; + const removals = this.aggregatedRemovals; + this.aggregatedChanges = new Set(); + this.aggregatedRemovals = new Set(); + this.emit("aggregated", changes, removals); + } +} + +module.exports = Watchpack; |
