You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. 'use strict';
  2. var chokidar = require('chokidar');
  3. var debounce = require('just-debounce');
  4. var asyncDone = require('async-done');
  5. var defaults = require('object.defaults/immutable');
  6. var isNegatedGlob = require('is-negated-glob');
  7. var anymatch = require('anymatch');
  8. var defaultOpts = {
  9. delay: 200,
  10. events: ['add', 'change', 'unlink'],
  11. ignored: [],
  12. ignoreInitial: true,
  13. queue: true,
  14. };
  15. function listenerCount(ee, evtName) {
  16. if (typeof ee.listenerCount === 'function') {
  17. return ee.listenerCount(evtName);
  18. }
  19. return ee.listeners(evtName).length;
  20. }
  21. function hasErrorListener(ee) {
  22. return listenerCount(ee, 'error') !== 0;
  23. }
  24. function exists(val) {
  25. return val != null;
  26. }
  27. function watch(glob, options, cb) {
  28. if (typeof options === 'function') {
  29. cb = options;
  30. options = {};
  31. }
  32. var opt = defaults(options, defaultOpts);
  33. if (!Array.isArray(opt.events)) {
  34. opt.events = [opt.events];
  35. }
  36. if (Array.isArray(glob)) {
  37. // We slice so we don't mutate the passed globs array
  38. glob = glob.slice();
  39. } else {
  40. glob = [glob];
  41. }
  42. var queued = false;
  43. var running = false;
  44. // These use sparse arrays to keep track of the index in the
  45. // original globs array
  46. var positives = new Array(glob.length);
  47. var negatives = new Array(glob.length);
  48. // Reverse the glob here so we don't end up with a positive
  49. // and negative glob in position 0 after a reverse
  50. glob.reverse().forEach(sortGlobs);
  51. function sortGlobs(globString, index) {
  52. var result = isNegatedGlob(globString);
  53. if (result.negated) {
  54. negatives[index] = result.pattern;
  55. } else {
  56. positives[index] = result.pattern;
  57. }
  58. }
  59. function shouldBeIgnored(path) {
  60. var positiveMatch = anymatch(positives, path, true);
  61. var negativeMatch = anymatch(negatives, path, true);
  62. // If negativeMatch is -1, that means it was never negated
  63. if (negativeMatch === -1) {
  64. return false;
  65. }
  66. // If the negative is "less than" the positive, that means
  67. // it came later in the glob array before we reversed them
  68. return negativeMatch < positiveMatch;
  69. }
  70. var toWatch = positives.filter(exists);
  71. // We only do add our custom `ignored` if there are some negative globs
  72. // TODO: I'm not sure how to test this
  73. if (negatives.some(exists)) {
  74. opt.ignored = [].concat(opt.ignored, shouldBeIgnored);
  75. }
  76. var watcher = chokidar.watch(toWatch, opt);
  77. function runComplete(err) {
  78. running = false;
  79. if (err && hasErrorListener(watcher)) {
  80. watcher.emit('error', err);
  81. }
  82. // If we have a run queued, start onChange again
  83. if (queued) {
  84. queued = false;
  85. onChange();
  86. }
  87. }
  88. function onChange() {
  89. if (running) {
  90. if (opt.queue) {
  91. queued = true;
  92. }
  93. return;
  94. }
  95. running = true;
  96. asyncDone(cb, runComplete);
  97. }
  98. var fn;
  99. if (typeof cb === 'function') {
  100. fn = debounce(onChange, opt.delay);
  101. }
  102. function watchEvent(eventName) {
  103. watcher.on(eventName, fn);
  104. }
  105. if (fn) {
  106. opt.events.forEach(watchEvent);
  107. }
  108. return watcher;
  109. }
  110. module.exports = watch;