index.js 3.4 KB

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