watcher.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. 'use strict'
  2. const chokidar = require('chokidar')
  3. const mm = require('minimatch')
  4. const expandBraces = require('expand-braces')
  5. const helper = require('./helper')
  6. const log = require('./logger').create('watcher')
  7. const DIR_SEP = require('path').sep
  8. function baseDirFromPattern (pattern) {
  9. return pattern
  10. .replace(/[/\\][^/\\]*\*.*$/, '') // remove parts with *
  11. .replace(/[/\\][^/\\]*[!+]\(.*$/, '') // remove parts with !(...) and +(...)
  12. .replace(/[/\\][^/\\]*\)\?.*$/, '') || DIR_SEP // remove parts with (...)?
  13. }
  14. function watchPatterns (patterns, watcher) {
  15. let pathsToWatch = new Set()
  16. // expand ['a/{b,c}'] to ['a/b', 'a/c']
  17. expandBraces(patterns)
  18. .forEach((path) => pathsToWatch.add(baseDirFromPattern(path)))
  19. pathsToWatch = Array.from(pathsToWatch)
  20. // watch only common parents, no sub paths
  21. pathsToWatch.forEach((path) => {
  22. if (!pathsToWatch.some((p) => p !== path && path.substr(0, p.length + 1) === p + DIR_SEP)) {
  23. watcher.add(path)
  24. log.debug('Watching "%s"', path)
  25. }
  26. })
  27. }
  28. function checkAnyPathMatch (patterns, path) {
  29. return patterns.some((pattern) => mm(path, pattern, {dot: true}))
  30. }
  31. function createIgnore (patterns, excludes) {
  32. return function (path, stat) {
  33. if (!stat || stat.isDirectory()) {
  34. return false
  35. }
  36. return !checkAnyPathMatch(patterns, path) || checkAnyPathMatch(excludes, path)
  37. }
  38. }
  39. function getWatchedPatterns (patterns) {
  40. return patterns
  41. .reduce((array, pattern) => {
  42. if (pattern.watched) {
  43. array.push(pattern.pattern)
  44. }
  45. return array
  46. }, [])
  47. }
  48. exports.watch = function (patterns, excludes, fileList, usePolling, emitter) {
  49. const watchedPatterns = getWatchedPatterns(patterns)
  50. const watcher = new chokidar.FSWatcher({
  51. usePolling: usePolling,
  52. ignorePermissionErrors: true,
  53. ignoreInitial: true,
  54. ignored: createIgnore(watchedPatterns, excludes)
  55. })
  56. watchPatterns(watchedPatterns, watcher)
  57. watcher
  58. .on('add', (path) => fileList.addFile(helper.normalizeWinPath(path)))
  59. .on('change', (path) => fileList.changeFile(helper.normalizeWinPath(path)))
  60. .on('unlink', (path) => fileList.removeFile(helper.normalizeWinPath(path)))
  61. .on('error', log.debug.bind(log))
  62. emitter.on('exit', (done) => {
  63. watcher.close()
  64. done()
  65. })
  66. return watcher
  67. }
  68. exports.watch.$inject = [
  69. 'config.files',
  70. 'config.exclude',
  71. 'fileList',
  72. 'config.usePolling',
  73. 'emitter'
  74. ]