parcel-bundler
Version:
Blazing fast, zero configuration web application bundler
197 lines (161 loc) • 5.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
const FSWatcher = require('fswatcher-child');
const Path = require('path');
/**
* This watcher wraps chokidar so that we watch directories rather than individual files on macOS.
* This prevents us from hitting EMFILE errors when running out of file descriptors.
* Chokidar does not have support for watching directories on non-macOS platforms, so we disable
* this behavior in order to prevent watching more individual files than necessary (e.g. node_modules).
*/
class Watcher {
constructor() {
// FS events on macOS are flakey in the tests, which write lots of files very quickly
// See https://github.com/paulmillr/chokidar/issues/612
this.shouldWatchDirs = process.platform === 'darwin' && process.env.NODE_ENV !== 'test';
this.watcher = new FSWatcher({
useFsEvents: this.shouldWatchDirs,
ignoreInitial: true,
ignorePermissionErrors: true,
ignored: /\.cache|\.git/
});
this.watchedDirectories = new Map();
this.stopped = false;
}
/**
* Find a parent directory of `path` which is already watched
*/
getWatchedParent(path) {
path = Path.dirname(path);
let root = Path.parse(path).root;
while (path !== root) {
if (this.watchedDirectories.has(path)) {
return path;
}
path = Path.dirname(path);
}
return null;
}
/**
* Find a list of child directories of `path` which are already watched
*/
getWatchedChildren(path) {
path = Path.dirname(path) + Path.sep;
let res = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.watchedDirectories.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
let dir = _step.value;
if (dir.startsWith(path)) {
res.push(dir);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return res;
}
/**
* Add a path to the watcher
*/
watch(path) {
if (this.shouldWatchDirs) {
// If there is no parent directory already watching this path, add a new watcher.
let parent = this.getWatchedParent(path);
if (!parent) {
// Find watchers on child directories, and remove them. They will be handled by the new parent watcher.
let children = this.getWatchedChildren(path);
let count = 1;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = children[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
let dir = _step2.value;
count += this.watchedDirectories.get(dir);
this.watcher._closePath(dir);
this.watchedDirectories.delete(dir);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
let dir = Path.dirname(path);
this.watcher.add(dir);
this.watchedDirectories.set(dir, count);
} else {
// Otherwise, increment the reference count of the parent watcher.
this.watchedDirectories.set(parent, this.watchedDirectories.get(parent) + 1);
}
} else {
this.watcher.add(path);
}
}
/**
* Remove a path from the watcher
*/
unwatch(path) {
if (this.shouldWatchDirs) {
let dir = this.getWatchedParent(path);
if (dir) {
// When the count of files watching a directory reaches zero, unwatch it.
let count = this.watchedDirectories.get(dir) - 1;
if (count === 0) {
this.watchedDirectories.delete(dir);
this.watcher.unwatch(dir);
} else {
this.watchedDirectories.set(dir, count);
}
}
} else {
this.watcher.unwatch(path);
}
}
/**
* Add an event handler
*/
on(event, callback) {
this.watcher.on(event, callback);
}
/**
* Add an event handler
*/
once(event, callback) {
this.watcher.once(event, callback);
}
/**
* Stop watching all paths
*/
stop() {
var _this = this;
return (0, _asyncToGenerator2.default)(function* () {
_this.stopped = true;
yield _this.watcher.close();
})();
}
}
module.exports = Watcher;