Index: imaps-frontend/node_modules/watchpack/lib/DirectoryWatcher.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/DirectoryWatcher.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/DirectoryWatcher.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,787 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const EventEmitter = require("events").EventEmitter;
+const fs = require("graceful-fs");
+const path = require("path");
+
+const watchEventSource = require("./watchEventSource");
+
+const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({});
+
+let FS_ACCURACY = 2000;
+
+const IS_OSX = require("os").platform() === "darwin";
+const WATCHPACK_POLLING = process.env.WATCHPACK_POLLING;
+const FORCE_POLLING =
+	`${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
+		? +WATCHPACK_POLLING
+		: !!WATCHPACK_POLLING && WATCHPACK_POLLING !== "false";
+
+function withoutCase(str) {
+	return str.toLowerCase();
+}
+
+function needCalls(times, callback) {
+	return function() {
+		if (--times === 0) {
+			return callback();
+		}
+	};
+}
+
+class Watcher extends EventEmitter {
+	constructor(directoryWatcher, filePath, startTime) {
+		super();
+		this.directoryWatcher = directoryWatcher;
+		this.path = filePath;
+		this.startTime = startTime && +startTime;
+	}
+
+	checkStartTime(mtime, initial) {
+		const startTime = this.startTime;
+		if (typeof startTime !== "number") return !initial;
+		return startTime <= mtime;
+	}
+
+	close() {
+		this.emit("closed");
+	}
+}
+
+class DirectoryWatcher extends EventEmitter {
+	constructor(watcherManager, directoryPath, options) {
+		super();
+		if (FORCE_POLLING) {
+			options.poll = FORCE_POLLING;
+		}
+		this.watcherManager = watcherManager;
+		this.options = options;
+		this.path = directoryPath;
+		// safeTime is the point in time after which reading is safe to be unchanged
+		// timestamp is a value that should be compared with another timestamp (mtime)
+		/** @type {Map<string, { safeTime: number, timestamp: number }} */
+		this.files = new Map();
+		/** @type {Map<string, number>} */
+		this.filesWithoutCase = new Map();
+		this.directories = new Map();
+		this.lastWatchEvent = 0;
+		this.initialScan = true;
+		this.ignored = options.ignored || (() => false);
+		this.nestedWatching = false;
+		this.polledWatching =
+			typeof options.poll === "number"
+				? options.poll
+				: options.poll
+				? 5007
+				: false;
+		this.timeout = undefined;
+		this.initialScanRemoved = new Set();
+		this.initialScanFinished = undefined;
+		/** @type {Map<string, Set<Watcher>>} */
+		this.watchers = new Map();
+		this.parentWatcher = null;
+		this.refs = 0;
+		this._activeEvents = new Map();
+		this.closed = false;
+		this.scanning = false;
+		this.scanAgain = false;
+		this.scanAgainInitial = false;
+
+		this.createWatcher();
+		this.doScan(true);
+	}
+
+	createWatcher() {
+		try {
+			if (this.polledWatching) {
+				this.watcher = {
+					close: () => {
+						if (this.timeout) {
+							clearTimeout(this.timeout);
+							this.timeout = undefined;
+						}
+					}
+				};
+			} else {
+				if (IS_OSX) {
+					this.watchInParentDirectory();
+				}
+				this.watcher = watchEventSource.watch(this.path);
+				this.watcher.on("change", this.onWatchEvent.bind(this));
+				this.watcher.on("error", this.onWatcherError.bind(this));
+			}
+		} catch (err) {
+			this.onWatcherError(err);
+		}
+	}
+
+	forEachWatcher(path, fn) {
+		const watchers = this.watchers.get(withoutCase(path));
+		if (watchers !== undefined) {
+			for (const w of watchers) {
+				fn(w);
+			}
+		}
+	}
+
+	setMissing(itemPath, initial, type) {
+		if (this.initialScan) {
+			this.initialScanRemoved.add(itemPath);
+		}
+
+		const oldDirectory = this.directories.get(itemPath);
+		if (oldDirectory) {
+			if (this.nestedWatching) oldDirectory.close();
+			this.directories.delete(itemPath);
+
+			this.forEachWatcher(itemPath, w => w.emit("remove", type));
+			if (!initial) {
+				this.forEachWatcher(this.path, w =>
+					w.emit("change", itemPath, null, type, initial)
+				);
+			}
+		}
+
+		const oldFile = this.files.get(itemPath);
+		if (oldFile) {
+			this.files.delete(itemPath);
+			const key = withoutCase(itemPath);
+			const count = this.filesWithoutCase.get(key) - 1;
+			if (count <= 0) {
+				this.filesWithoutCase.delete(key);
+				this.forEachWatcher(itemPath, w => w.emit("remove", type));
+			} else {
+				this.filesWithoutCase.set(key, count);
+			}
+
+			if (!initial) {
+				this.forEachWatcher(this.path, w =>
+					w.emit("change", itemPath, null, type, initial)
+				);
+			}
+		}
+	}
+
+	setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) {
+		const now = Date.now();
+
+		if (this.ignored(filePath)) return;
+
+		const old = this.files.get(filePath);
+
+		let safeTime, accuracy;
+		if (initial) {
+			safeTime = Math.min(now, mtime) + FS_ACCURACY;
+			accuracy = FS_ACCURACY;
+		} else {
+			safeTime = now;
+			accuracy = 0;
+
+			if (old && old.timestamp === mtime && mtime + FS_ACCURACY < now) {
+				// We are sure that mtime is untouched
+				// This can be caused by some file attribute change
+				// e. g. when access time has been changed
+				// but the file content is untouched
+				return;
+			}
+		}
+
+		if (ignoreWhenEqual && old && old.timestamp === mtime) return;
+
+		this.files.set(filePath, {
+			safeTime,
+			accuracy,
+			timestamp: mtime
+		});
+
+		if (!old) {
+			const key = withoutCase(filePath);
+			const count = this.filesWithoutCase.get(key);
+			this.filesWithoutCase.set(key, (count || 0) + 1);
+			if (count !== undefined) {
+				// There is already a file with case-insensitive-equal name
+				// On a case-insensitive filesystem we may miss the renaming
+				// when only casing is changed.
+				// To be sure that our information is correct
+				// we trigger a rescan here
+				this.doScan(false);
+			}
+
+			this.forEachWatcher(filePath, w => {
+				if (!initial || w.checkStartTime(safeTime, initial)) {
+					w.emit("change", mtime, type);
+				}
+			});
+		} else if (!initial) {
+			this.forEachWatcher(filePath, w => w.emit("change", mtime, type));
+		}
+		this.forEachWatcher(this.path, w => {
+			if (!initial || w.checkStartTime(safeTime, initial)) {
+				w.emit("change", filePath, safeTime, type, initial);
+			}
+		});
+	}
+
+	setDirectory(directoryPath, birthtime, initial, type) {
+		if (this.ignored(directoryPath)) return;
+		if (directoryPath === this.path) {
+			if (!initial) {
+				this.forEachWatcher(this.path, w =>
+					w.emit("change", directoryPath, birthtime, type, initial)
+				);
+			}
+		} else {
+			const old = this.directories.get(directoryPath);
+			if (!old) {
+				const now = Date.now();
+
+				if (this.nestedWatching) {
+					this.createNestedWatcher(directoryPath);
+				} else {
+					this.directories.set(directoryPath, true);
+				}
+
+				let safeTime;
+				if (initial) {
+					safeTime = Math.min(now, birthtime) + FS_ACCURACY;
+				} else {
+					safeTime = now;
+				}
+
+				this.forEachWatcher(directoryPath, w => {
+					if (!initial || w.checkStartTime(safeTime, false)) {
+						w.emit("change", birthtime, type);
+					}
+				});
+				this.forEachWatcher(this.path, w => {
+					if (!initial || w.checkStartTime(safeTime, initial)) {
+						w.emit("change", directoryPath, safeTime, type, initial);
+					}
+				});
+			}
+		}
+	}
+
+	createNestedWatcher(directoryPath) {
+		const watcher = this.watcherManager.watchDirectory(directoryPath, 1);
+		watcher.on("change", (filePath, mtime, type, initial) => {
+			this.forEachWatcher(this.path, w => {
+				if (!initial || w.checkStartTime(mtime, initial)) {
+					w.emit("change", filePath, mtime, type, initial);
+				}
+			});
+		});
+		this.directories.set(directoryPath, watcher);
+	}
+
+	setNestedWatching(flag) {
+		if (this.nestedWatching !== !!flag) {
+			this.nestedWatching = !!flag;
+			if (this.nestedWatching) {
+				for (const directory of this.directories.keys()) {
+					this.createNestedWatcher(directory);
+				}
+			} else {
+				for (const [directory, watcher] of this.directories) {
+					watcher.close();
+					this.directories.set(directory, true);
+				}
+			}
+		}
+	}
+
+	watch(filePath, startTime) {
+		const key = withoutCase(filePath);
+		let watchers = this.watchers.get(key);
+		if (watchers === undefined) {
+			watchers = new Set();
+			this.watchers.set(key, watchers);
+		}
+		this.refs++;
+		const watcher = new Watcher(this, filePath, startTime);
+		watcher.on("closed", () => {
+			if (--this.refs <= 0) {
+				this.close();
+				return;
+			}
+			watchers.delete(watcher);
+			if (watchers.size === 0) {
+				this.watchers.delete(key);
+				if (this.path === filePath) this.setNestedWatching(false);
+			}
+		});
+		watchers.add(watcher);
+		let safeTime;
+		if (filePath === this.path) {
+			this.setNestedWatching(true);
+			safeTime = this.lastWatchEvent;
+			for (const entry of this.files.values()) {
+				fixupEntryAccuracy(entry);
+				safeTime = Math.max(safeTime, entry.safeTime);
+			}
+		} else {
+			const entry = this.files.get(filePath);
+			if (entry) {
+				fixupEntryAccuracy(entry);
+				safeTime = entry.safeTime;
+			} else {
+				safeTime = 0;
+			}
+		}
+		if (safeTime) {
+			if (safeTime >= startTime) {
+				process.nextTick(() => {
+					if (this.closed) return;
+					if (filePath === this.path) {
+						watcher.emit(
+							"change",
+							filePath,
+							safeTime,
+							"watch (outdated on attach)",
+							true
+						);
+					} else {
+						watcher.emit(
+							"change",
+							safeTime,
+							"watch (outdated on attach)",
+							true
+						);
+					}
+				});
+			}
+		} else if (this.initialScan) {
+			if (this.initialScanRemoved.has(filePath)) {
+				process.nextTick(() => {
+					if (this.closed) return;
+					watcher.emit("remove");
+				});
+			}
+		} else if (
+			filePath !== this.path &&
+			!this.directories.has(filePath) &&
+			watcher.checkStartTime(this.initialScanFinished, false)
+		) {
+			process.nextTick(() => {
+				if (this.closed) return;
+				watcher.emit("initial-missing", "watch (missing on attach)");
+			});
+		}
+		return watcher;
+	}
+
+	onWatchEvent(eventType, filename) {
+		if (this.closed) return;
+		if (!filename) {
+			// In some cases no filename is provided
+			// This seem to happen on windows
+			// So some event happened but we don't know which file is affected
+			// We have to do a full scan of the directory
+			this.doScan(false);
+			return;
+		}
+
+		const filePath = path.join(this.path, filename);
+		if (this.ignored(filePath)) return;
+
+		if (this._activeEvents.get(filename) === undefined) {
+			this._activeEvents.set(filename, false);
+			const checkStats = () => {
+				if (this.closed) return;
+				this._activeEvents.set(filename, false);
+				fs.lstat(filePath, (err, stats) => {
+					if (this.closed) return;
+					if (this._activeEvents.get(filename) === true) {
+						process.nextTick(checkStats);
+						return;
+					}
+					this._activeEvents.delete(filename);
+					// ENOENT happens when the file/directory doesn't exist
+					// EPERM happens when the containing directory doesn't exist
+					if (err) {
+						if (
+							err.code !== "ENOENT" &&
+							err.code !== "EPERM" &&
+							err.code !== "EBUSY"
+						) {
+							this.onStatsError(err);
+						} else {
+							if (filename === path.basename(this.path)) {
+								// This may indicate that the directory itself was removed
+								if (!fs.existsSync(this.path)) {
+									this.onDirectoryRemoved("stat failed");
+								}
+							}
+						}
+					}
+					this.lastWatchEvent = Date.now();
+					if (!stats) {
+						this.setMissing(filePath, false, eventType);
+					} else if (stats.isDirectory()) {
+						this.setDirectory(
+							filePath,
+							+stats.birthtime || 1,
+							false,
+							eventType
+						);
+					} else if (stats.isFile() || stats.isSymbolicLink()) {
+						if (stats.mtime) {
+							ensureFsAccuracy(stats.mtime);
+						}
+						this.setFileTime(
+							filePath,
+							+stats.mtime || +stats.ctime || 1,
+							false,
+							false,
+							eventType
+						);
+					}
+				});
+			};
+			process.nextTick(checkStats);
+		} else {
+			this._activeEvents.set(filename, true);
+		}
+	}
+
+	onWatcherError(err) {
+		if (this.closed) return;
+		if (err) {
+			if (err.code !== "EPERM" && err.code !== "ENOENT") {
+				console.error("Watchpack Error (watcher): " + err);
+			}
+			this.onDirectoryRemoved("watch error");
+		}
+	}
+
+	onStatsError(err) {
+		if (err) {
+			console.error("Watchpack Error (stats): " + err);
+		}
+	}
+
+	onScanError(err) {
+		if (err) {
+			console.error("Watchpack Error (initial scan): " + err);
+		}
+		this.onScanFinished();
+	}
+
+	onScanFinished() {
+		if (this.polledWatching) {
+			this.timeout = setTimeout(() => {
+				if (this.closed) return;
+				this.doScan(false);
+			}, this.polledWatching);
+		}
+	}
+
+	onDirectoryRemoved(reason) {
+		if (this.watcher) {
+			this.watcher.close();
+			this.watcher = null;
+		}
+		this.watchInParentDirectory();
+		const type = `directory-removed (${reason})`;
+		for (const directory of this.directories.keys()) {
+			this.setMissing(directory, null, type);
+		}
+		for (const file of this.files.keys()) {
+			this.setMissing(file, null, type);
+		}
+	}
+
+	watchInParentDirectory() {
+		if (!this.parentWatcher) {
+			const parentDir = path.dirname(this.path);
+			// avoid watching in the root directory
+			// removing directories in the root directory is not supported
+			if (path.dirname(parentDir) === parentDir) return;
+
+			this.parentWatcher = this.watcherManager.watchFile(this.path, 1);
+			this.parentWatcher.on("change", (mtime, type) => {
+				if (this.closed) return;
+
+				// On non-osx platforms we don't need this watcher to detect
+				// directory removal, as an EPERM error indicates that
+				if ((!IS_OSX || this.polledWatching) && this.parentWatcher) {
+					this.parentWatcher.close();
+					this.parentWatcher = null;
+				}
+				// Try to create the watcher when parent directory is found
+				if (!this.watcher) {
+					this.createWatcher();
+					this.doScan(false);
+
+					// directory was created so we emit an event
+					this.forEachWatcher(this.path, w =>
+						w.emit("change", this.path, mtime, type, false)
+					);
+				}
+			});
+			this.parentWatcher.on("remove", () => {
+				this.onDirectoryRemoved("parent directory removed");
+			});
+		}
+	}
+
+	doScan(initial) {
+		if (this.scanning) {
+			if (this.scanAgain) {
+				if (!initial) this.scanAgainInitial = false;
+			} else {
+				this.scanAgain = true;
+				this.scanAgainInitial = initial;
+			}
+			return;
+		}
+		this.scanning = true;
+		if (this.timeout) {
+			clearTimeout(this.timeout);
+			this.timeout = undefined;
+		}
+		process.nextTick(() => {
+			if (this.closed) return;
+			fs.readdir(this.path, (err, items) => {
+				if (this.closed) return;
+				if (err) {
+					if (err.code === "ENOENT" || err.code === "EPERM") {
+						this.onDirectoryRemoved("scan readdir failed");
+					} else {
+						this.onScanError(err);
+					}
+					this.initialScan = false;
+					this.initialScanFinished = Date.now();
+					if (initial) {
+						for (const watchers of this.watchers.values()) {
+							for (const watcher of watchers) {
+								if (watcher.checkStartTime(this.initialScanFinished, false)) {
+									watcher.emit(
+										"initial-missing",
+										"scan (parent directory missing in initial scan)"
+									);
+								}
+							}
+						}
+					}
+					if (this.scanAgain) {
+						this.scanAgain = false;
+						this.doScan(this.scanAgainInitial);
+					} else {
+						this.scanning = false;
+					}
+					return;
+				}
+				const itemPaths = new Set(
+					items.map(item => path.join(this.path, item.normalize("NFC")))
+				);
+				for (const file of this.files.keys()) {
+					if (!itemPaths.has(file)) {
+						this.setMissing(file, initial, "scan (missing)");
+					}
+				}
+				for (const directory of this.directories.keys()) {
+					if (!itemPaths.has(directory)) {
+						this.setMissing(directory, initial, "scan (missing)");
+					}
+				}
+				if (this.scanAgain) {
+					// Early repeat of scan
+					this.scanAgain = false;
+					this.doScan(initial);
+					return;
+				}
+				const itemFinished = needCalls(itemPaths.size + 1, () => {
+					if (this.closed) return;
+					this.initialScan = false;
+					this.initialScanRemoved = null;
+					this.initialScanFinished = Date.now();
+					if (initial) {
+						const missingWatchers = new Map(this.watchers);
+						missingWatchers.delete(withoutCase(this.path));
+						for (const item of itemPaths) {
+							missingWatchers.delete(withoutCase(item));
+						}
+						for (const watchers of missingWatchers.values()) {
+							for (const watcher of watchers) {
+								if (watcher.checkStartTime(this.initialScanFinished, false)) {
+									watcher.emit(
+										"initial-missing",
+										"scan (missing in initial scan)"
+									);
+								}
+							}
+						}
+					}
+					if (this.scanAgain) {
+						this.scanAgain = false;
+						this.doScan(this.scanAgainInitial);
+					} else {
+						this.scanning = false;
+						this.onScanFinished();
+					}
+				});
+				for (const itemPath of itemPaths) {
+					fs.lstat(itemPath, (err2, stats) => {
+						if (this.closed) return;
+						if (err2) {
+							if (
+								err2.code === "ENOENT" ||
+								err2.code === "EPERM" ||
+								err2.code === "EACCES" ||
+								err2.code === "EBUSY"
+							) {
+								this.setMissing(itemPath, initial, "scan (" + err2.code + ")");
+							} else {
+								this.onScanError(err2);
+							}
+							itemFinished();
+							return;
+						}
+						if (stats.isFile() || stats.isSymbolicLink()) {
+							if (stats.mtime) {
+								ensureFsAccuracy(stats.mtime);
+							}
+							this.setFileTime(
+								itemPath,
+								+stats.mtime || +stats.ctime || 1,
+								initial,
+								true,
+								"scan (file)"
+							);
+						} else if (stats.isDirectory()) {
+							if (!initial || !this.directories.has(itemPath))
+								this.setDirectory(
+									itemPath,
+									+stats.birthtime || 1,
+									initial,
+									"scan (dir)"
+								);
+						}
+						itemFinished();
+					});
+				}
+				itemFinished();
+			});
+		});
+	}
+
+	getTimes() {
+		const obj = Object.create(null);
+		let safeTime = this.lastWatchEvent;
+		for (const [file, entry] of this.files) {
+			fixupEntryAccuracy(entry);
+			safeTime = Math.max(safeTime, entry.safeTime);
+			obj[file] = Math.max(entry.safeTime, entry.timestamp);
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				const times = w.directoryWatcher.getTimes();
+				for (const file of Object.keys(times)) {
+					const time = times[file];
+					safeTime = Math.max(safeTime, time);
+					obj[file] = time;
+				}
+			}
+			obj[this.path] = safeTime;
+		}
+		if (!this.initialScan) {
+			for (const watchers of this.watchers.values()) {
+				for (const watcher of watchers) {
+					const path = watcher.path;
+					if (!Object.prototype.hasOwnProperty.call(obj, path)) {
+						obj[path] = null;
+					}
+				}
+			}
+		}
+		return obj;
+	}
+
+	collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
+		let safeTime = this.lastWatchEvent;
+		for (const [file, entry] of this.files) {
+			fixupEntryAccuracy(entry);
+			safeTime = Math.max(safeTime, entry.safeTime);
+			fileTimestamps.set(file, entry);
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				safeTime = Math.max(
+					safeTime,
+					w.directoryWatcher.collectTimeInfoEntries(
+						fileTimestamps,
+						directoryTimestamps
+					)
+				);
+			}
+			fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+			directoryTimestamps.set(this.path, {
+				safeTime
+			});
+		} else {
+			for (const dir of this.directories.keys()) {
+				// No additional info about this directory
+				// but maybe another DirectoryWatcher has info
+				fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
+				if (!directoryTimestamps.has(dir))
+					directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
+			}
+			fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+			directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
+		}
+		if (!this.initialScan) {
+			for (const watchers of this.watchers.values()) {
+				for (const watcher of watchers) {
+					const path = watcher.path;
+					if (!fileTimestamps.has(path)) {
+						fileTimestamps.set(path, null);
+					}
+				}
+			}
+		}
+		return safeTime;
+	}
+
+	close() {
+		this.closed = true;
+		this.initialScan = false;
+		if (this.watcher) {
+			this.watcher.close();
+			this.watcher = null;
+		}
+		if (this.nestedWatching) {
+			for (const w of this.directories.values()) {
+				w.close();
+			}
+			this.directories.clear();
+		}
+		if (this.parentWatcher) {
+			this.parentWatcher.close();
+			this.parentWatcher = null;
+		}
+		this.emit("closed");
+	}
+}
+
+module.exports = DirectoryWatcher;
+module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY;
+
+function fixupEntryAccuracy(entry) {
+	if (entry.accuracy > FS_ACCURACY) {
+		entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
+		entry.accuracy = FS_ACCURACY;
+	}
+}
+
+function ensureFsAccuracy(mtime) {
+	if (!mtime) return;
+	if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
+	else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
+	else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
+	else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
+}
Index: imaps-frontend/node_modules/watchpack/lib/LinkResolver.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/LinkResolver.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/LinkResolver.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,107 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const fs = require("fs");
+const path = require("path");
+
+// macOS, Linux, and Windows all rely on these errors
+const EXPECTED_ERRORS = new Set(["EINVAL", "ENOENT"]);
+
+// On Windows there is also this error in some cases
+if (process.platform === "win32") EXPECTED_ERRORS.add("UNKNOWN");
+
+class LinkResolver {
+	constructor() {
+		this.cache = new Map();
+	}
+
+	/**
+	 * @param {string} file path to file or directory
+	 * @returns {string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file)
+	 */
+	resolve(file) {
+		const cacheEntry = this.cache.get(file);
+		if (cacheEntry !== undefined) {
+			return cacheEntry;
+		}
+		const parent = path.dirname(file);
+		if (parent === file) {
+			// At root of filesystem there can't be a link
+			const result = Object.freeze([file]);
+			this.cache.set(file, result);
+			return result;
+		}
+		// resolve the parent directory to find links there and get the real path
+		const parentResolved = this.resolve(parent);
+		let realFile = file;
+
+		// is the parent directory really somewhere else?
+		if (parentResolved[0] !== parent) {
+			// get the real location of file
+			const basename = path.basename(file);
+			realFile = path.resolve(parentResolved[0], basename);
+		}
+		// try to read the link content
+		try {
+			const linkContent = fs.readlinkSync(realFile);
+
+			// resolve the link content relative to the parent directory
+			const resolvedLink = path.resolve(parentResolved[0], linkContent);
+
+			// recursive resolve the link content for more links in the structure
+			const linkResolved = this.resolve(resolvedLink);
+
+			// merge parent and link resolve results
+			let result;
+			if (linkResolved.length > 1 && parentResolved.length > 1) {
+				// when both contain links we need to duplicate them with a Set
+				const resultSet = new Set(linkResolved);
+				// add the link
+				resultSet.add(realFile);
+				// add all symlinks of the parent
+				for (let i = 1; i < parentResolved.length; i++) {
+					resultSet.add(parentResolved[i]);
+				}
+				result = Object.freeze(Array.from(resultSet));
+			} else if (parentResolved.length > 1) {
+				// we have links in the parent but not for the link content location
+				result = parentResolved.slice();
+				result[0] = linkResolved[0];
+				// add the link
+				result.push(realFile);
+				Object.freeze(result);
+			} else if (linkResolved.length > 1) {
+				// we can return the link content location result
+				result = linkResolved.slice();
+				// add the link
+				result.push(realFile);
+				Object.freeze(result);
+			} else {
+				// neither link content location nor parent have links
+				// this link is the only link here
+				result = Object.freeze([
+					// the resolve real location
+					linkResolved[0],
+					// add the link
+					realFile
+				]);
+			}
+			this.cache.set(file, result);
+			return result;
+		} catch (e) {
+			if (!EXPECTED_ERRORS.has(e.code)) {
+				throw e;
+			}
+			// no link
+			const result = parentResolved.slice();
+			result[0] = realFile;
+			Object.freeze(result);
+			this.cache.set(file, result);
+			return result;
+		}
+	}
+}
+module.exports = LinkResolver;
Index: imaps-frontend/node_modules/watchpack/lib/getWatcherManager.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/getWatcherManager.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/getWatcherManager.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,52 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const path = require("path");
+const DirectoryWatcher = require("./DirectoryWatcher");
+
+class WatcherManager {
+	constructor(options) {
+		this.options = options;
+		this.directoryWatchers = new Map();
+	}
+
+	getDirectoryWatcher(directory) {
+		const watcher = this.directoryWatchers.get(directory);
+		if (watcher === undefined) {
+			const newWatcher = new DirectoryWatcher(this, directory, this.options);
+			this.directoryWatchers.set(directory, newWatcher);
+			newWatcher.on("closed", () => {
+				this.directoryWatchers.delete(directory);
+			});
+			return newWatcher;
+		}
+		return watcher;
+	}
+
+	watchFile(p, startTime) {
+		const directory = path.dirname(p);
+		if (directory === p) return null;
+		return this.getDirectoryWatcher(directory).watch(p, startTime);
+	}
+
+	watchDirectory(directory, startTime) {
+		return this.getDirectoryWatcher(directory).watch(directory, startTime);
+	}
+}
+
+const watcherManagers = new WeakMap();
+/**
+ * @param {object} options options
+ * @returns {WatcherManager} the watcher manager
+ */
+module.exports = options => {
+	const watcherManager = watcherManagers.get(options);
+	if (watcherManager !== undefined) return watcherManager;
+	const newWatcherManager = new WatcherManager(options);
+	watcherManagers.set(options, newWatcherManager);
+	return newWatcherManager;
+};
+module.exports.WatcherManager = WatcherManager;
Index: imaps-frontend/node_modules/watchpack/lib/reducePlan.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/reducePlan.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/reducePlan.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,138 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const path = require("path");
+
+/**
+ * @template T
+ * @typedef {Object} TreeNode
+ * @property {string} filePath
+ * @property {TreeNode} parent
+ * @property {TreeNode[]} children
+ * @property {number} entries
+ * @property {boolean} active
+ * @property {T[] | T | undefined} value
+ */
+
+/**
+ * @template T
+ * @param {Map<string, T[] | T} plan
+ * @param {number} limit
+ * @returns {Map<string, Map<T, string>>} the new plan
+ */
+module.exports = (plan, limit) => {
+	const treeMap = new Map();
+	// Convert to tree
+	for (const [filePath, value] of plan) {
+		treeMap.set(filePath, {
+			filePath,
+			parent: undefined,
+			children: undefined,
+			entries: 1,
+			active: true,
+			value
+		});
+	}
+	let currentCount = treeMap.size;
+	// Create parents and calculate sum of entries
+	for (const node of treeMap.values()) {
+		const parentPath = path.dirname(node.filePath);
+		if (parentPath !== node.filePath) {
+			let parent = treeMap.get(parentPath);
+			if (parent === undefined) {
+				parent = {
+					filePath: parentPath,
+					parent: undefined,
+					children: [node],
+					entries: node.entries,
+					active: false,
+					value: undefined
+				};
+				treeMap.set(parentPath, parent);
+				node.parent = parent;
+			} else {
+				node.parent = parent;
+				if (parent.children === undefined) {
+					parent.children = [node];
+				} else {
+					parent.children.push(node);
+				}
+				do {
+					parent.entries += node.entries;
+					parent = parent.parent;
+				} while (parent);
+			}
+		}
+	}
+	// Reduce until limit reached
+	while (currentCount > limit) {
+		// Select node that helps reaching the limit most effectively without overmerging
+		const overLimit = currentCount - limit;
+		let bestNode = undefined;
+		let bestCost = Infinity;
+		for (const node of treeMap.values()) {
+			if (node.entries <= 1 || !node.children || !node.parent) continue;
+			if (node.children.length === 0) continue;
+			if (node.children.length === 1 && !node.value) continue;
+			// Try to select the node with has just a bit more entries than we need to reduce
+			// When just a bit more is over 30% over the limit,
+			// also consider just a bit less entries then we need to reduce
+			const cost =
+				node.entries - 1 >= overLimit
+					? node.entries - 1 - overLimit
+					: overLimit - node.entries + 1 + limit * 0.3;
+			if (cost < bestCost) {
+				bestNode = node;
+				bestCost = cost;
+			}
+		}
+		if (!bestNode) break;
+		// Merge all children
+		const reduction = bestNode.entries - 1;
+		bestNode.active = true;
+		bestNode.entries = 1;
+		currentCount -= reduction;
+		let parent = bestNode.parent;
+		while (parent) {
+			parent.entries -= reduction;
+			parent = parent.parent;
+		}
+		const queue = new Set(bestNode.children);
+		for (const node of queue) {
+			node.active = false;
+			node.entries = 0;
+			if (node.children) {
+				for (const child of node.children) queue.add(child);
+			}
+		}
+	}
+	// Write down new plan
+	const newPlan = new Map();
+	for (const rootNode of treeMap.values()) {
+		if (!rootNode.active) continue;
+		const map = new Map();
+		const queue = new Set([rootNode]);
+		for (const node of queue) {
+			if (node.active && node !== rootNode) continue;
+			if (node.value) {
+				if (Array.isArray(node.value)) {
+					for (const item of node.value) {
+						map.set(item, node.filePath);
+					}
+				} else {
+					map.set(node.value, node.filePath);
+				}
+			}
+			if (node.children) {
+				for (const child of node.children) {
+					queue.add(child);
+				}
+			}
+		}
+		newPlan.set(rootNode.filePath, map);
+	}
+	return newPlan;
+};
Index: imaps-frontend/node_modules/watchpack/lib/watchEventSource.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/watchEventSource.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/watchEventSource.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,333 @@
+/*
+	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;
+
+const watcherLimit =
+	+process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 2000 : 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();
+
+class DirectWatcher {
+	constructor(filePath) {
+		this.filePath = filePath;
+		this.watchers = new Set();
+		this.watcher = undefined;
+		try {
+			const watcher = fs.watch(filePath);
+			this.watcher = watcher;
+			watcher.on("change", (type, filename) => {
+				for (const w of this.watchers) {
+					w.emit("change", type, filename);
+				}
+			});
+			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;
+};
Index: imaps-frontend/node_modules/watchpack/lib/watchpack.js
===================================================================
--- imaps-frontend/node_modules/watchpack/lib/watchpack.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
+++ imaps-frontend/node_modules/watchpack/lib/watchpack.js	(revision 79a03170cdde4e801d12257ee86723774970427b)
@@ -0,0 +1,393 @@
+/*
+	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 => {
+	if (ignored.length === 0) {
+		return;
+	}
+	const source = globToRegExp(ignored, { globstar: true, extended: true })
+		.source;
+	return source.slice(0, source.length - 1) + "(?:$|\\/)";
+};
+
+const ignoredToFunction = ignored => {
+	if (Array.isArray(ignored)) {
+		const stringRegexps = ignored.map(i => stringToRegexp(i)).filter(Boolean);
+		if (stringRegexps.length === 0) {
+			return () => false;
+		}
+		const regexp = new RegExp(stringRegexps.join("|"));
+		return x => regexp.test(x.replace(/\\/g, "/"));
+	} else if (typeof ignored === "string") {
+		const stringRegexp = stringToRegexp(ignored);
+		if (!stringRegexp) {
+			return () => false;
+		}
+		const regexp = new RegExp(stringRegexp);
+		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;
