mirror of
https://github.com/SrIzan10/nodemon.git
synced 2026-05-01 10:55:09 +00:00
Fixes #1317 Originally a new Set was generated upon every individual file (or dir) watched. This typically doesn't take a long time, but if there's 10,000 files to watch, it's very, very slow. Moving this logic out to the ready event instead, cuts all the processing time of the watch right down.
238 lines
5.9 KiB
JavaScript
238 lines
5.9 KiB
JavaScript
module.exports.watch = watch;
|
|
module.exports.resetWatchers = resetWatchers;
|
|
|
|
var debug = require('debug')('nodemon:watch');
|
|
var debugRoot = require('debug')('nodemon');
|
|
var chokidar = require('chokidar');
|
|
var undefsafe = require('undefsafe');
|
|
var config = require('../config');
|
|
var path = require('path');
|
|
const fs = require('fs');
|
|
var utils = require('../utils');
|
|
var bus = utils.bus;
|
|
var match = require('./match');
|
|
var watchers = [];
|
|
var debouncedBus;
|
|
|
|
bus.on('reset', resetWatchers);
|
|
|
|
function resetWatchers() {
|
|
debugRoot('resetting watchers');
|
|
watchers.forEach(function (watcher) {
|
|
watcher.close();
|
|
});
|
|
watchers = [];
|
|
}
|
|
|
|
function watch() {
|
|
if (watchers.length) {
|
|
debug('early exit on watch, still watching (%s)', watchers.length);
|
|
return;
|
|
}
|
|
|
|
var dirs = [].slice.call(config.dirs);
|
|
|
|
debugRoot('start watch on: %s', dirs.join(', '));
|
|
const rootIgnored = config.options.ignore;
|
|
debugRoot('ignored', rootIgnored);
|
|
|
|
var promises = [];
|
|
var watchedFiles = [];
|
|
|
|
const promise = new Promise(function (resolve) {
|
|
const dotFilePattern = /[/\\]\./;
|
|
var ignored = Array.from(rootIgnored);
|
|
const addDotFile = dirs.filter(dir => dir.match(dotFilePattern));
|
|
|
|
// don't ignore dotfiles if explicitly watched.
|
|
if (addDotFile.length === 0) {
|
|
ignored.push(dotFilePattern);
|
|
}
|
|
|
|
dirs = dirs.map(dir => {
|
|
// if the directory is a file, it somehow causes
|
|
// windows to lose the filename upon change
|
|
if (fs.statSync(dir).isFile()) {
|
|
dir = path.dirname(dir);
|
|
}
|
|
|
|
return dir;
|
|
});
|
|
|
|
var watchOptions = {
|
|
ignorePermissionErrors: true,
|
|
ignored: ignored,
|
|
persistent: true,
|
|
usePolling: config.options.legacyWatch || false,
|
|
interval: config.options.pollingInterval,
|
|
};
|
|
|
|
if (utils.isWindows) {
|
|
watchOptions.disableGlobbing = true;
|
|
}
|
|
|
|
if (process.env.TEST) {
|
|
watchOptions.useFsEvents = false;
|
|
}
|
|
|
|
var watcher = chokidar.watch(
|
|
dirs,
|
|
Object.assign({}, watchOptions, config.watchOptions || {})
|
|
);
|
|
|
|
watcher.ready = false;
|
|
|
|
var total = 0;
|
|
|
|
watcher.on('change', filterAndRestart);
|
|
watcher.on('add', function (file) {
|
|
if (watcher.ready) {
|
|
return filterAndRestart(file);
|
|
}
|
|
|
|
watchedFiles.push(file);
|
|
bus.emit('watching', file);
|
|
debug('watching dir: %s', file);
|
|
});
|
|
watcher.on('ready', function () {
|
|
watchedFiles = Array.from(new Set(watchedFiles)); // ensure no dupes
|
|
total = watchedFiles.length;
|
|
watcher.ready = true;
|
|
resolve(total);
|
|
debugRoot('watch is complete');
|
|
});
|
|
|
|
watcher.on('error', function (error) {
|
|
if (error.code === 'EINVAL') {
|
|
utils.log.error(
|
|
'Internal watch failed. Likely cause: too many ' +
|
|
'files being watched (perhaps from the root of a drive?\n' +
|
|
'See https://github.com/paulmillr/chokidar/issues/229 for details'
|
|
);
|
|
} else {
|
|
utils.log.error('Internal watch failed: ' + error.message);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
|
|
watchers.push(watcher);
|
|
});
|
|
|
|
return promise.catch(e => {
|
|
setTimeout(() => {
|
|
throw e;
|
|
});
|
|
}).then(function (res) {
|
|
utils.log.detail(`watching ${watchedFiles.length} file${
|
|
watchedFiles.length === 1 ? '' : 's'}`);
|
|
return watchedFiles;
|
|
});
|
|
}
|
|
|
|
function filterAndRestart(files) {
|
|
if (!Array.isArray(files)) {
|
|
files = [files];
|
|
}
|
|
if (files.length) {
|
|
var cwd = process.cwd();
|
|
if (this.options && this.options.cwd) {
|
|
cwd = this.options.cwd;
|
|
}
|
|
|
|
utils.log.detail(
|
|
'files triggering change check: ' +
|
|
files
|
|
.map(function (file) {
|
|
const res = path.relative(cwd, file);
|
|
return res;
|
|
})
|
|
.join(', ')
|
|
);
|
|
|
|
files = files.map(file => {
|
|
return path.relative(process.cwd(), path.relative(cwd, file));
|
|
});
|
|
|
|
if (utils.isWindows) {
|
|
// ensure the drive letter is in uppercase (c:\foo -> C:\foo)
|
|
files = files.map(function (f) {
|
|
return f[0].toUpperCase() + f.slice(1);
|
|
});
|
|
}
|
|
|
|
|
|
debug('filterAndRestart on', files);
|
|
|
|
var matched = match(
|
|
files,
|
|
config.options.monitor,
|
|
undefsafe(config, 'options.execOptions.ext')
|
|
);
|
|
|
|
debug('matched?', JSON.stringify(matched));
|
|
|
|
// if there's no matches, then test to see if the changed file is the
|
|
// running script, if so, let's allow a restart
|
|
if (config.options.execOptions.script) {
|
|
const script = path.resolve(config.options.execOptions.script);
|
|
if (matched.result.length === 0 && script) {
|
|
const length = script.length;
|
|
files.find(file => {
|
|
if (file.substr(-length, length) === script) {
|
|
matched = {
|
|
result: [file],
|
|
total: 1,
|
|
};
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
utils.log.detail(
|
|
'changes after filters (before/after): ' +
|
|
[files.length, matched.result.length].join('/')
|
|
);
|
|
|
|
// reset the last check so we're only looking at recently modified files
|
|
config.lastStarted = Date.now();
|
|
|
|
if (matched.result.length) {
|
|
if (config.options.delay > 0) {
|
|
utils.log.detail('delaying restart for ' + config.options.delay + 'ms');
|
|
if (debouncedBus === undefined) {
|
|
debouncedBus = debounce(restartBus, config.options.delay);
|
|
}
|
|
debouncedBus(matched);
|
|
} else {
|
|
return restartBus(matched);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function restartBus(matched) {
|
|
utils.log.status('restarting due to changes...');
|
|
matched.result.map(function (file) {
|
|
utils.log.detail(path.relative(process.cwd(), file));
|
|
});
|
|
|
|
if (config.options.verbose) {
|
|
utils.log._log('');
|
|
}
|
|
|
|
bus.emit('restart', matched.result);
|
|
}
|
|
|
|
function debounce(fn, delay) {
|
|
var timer = null;
|
|
return function () {
|
|
var context = this;
|
|
var args = arguments;
|
|
clearTimeout(timer);
|
|
timer = setTimeout(function () {
|
|
fn.apply(context, args);
|
|
}, delay);
|
|
};
|
|
}
|