summaryrefslogtreecommitdiff
path: root/node_modules/watchpack/lib/watchpack.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/watchpack/lib/watchpack.js')
-rw-r--r--node_modules/watchpack/lib/watchpack.js383
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;