Серия интерпретаций исходного кода чокидар

Node.js

Цель

Многие инструменты (vs code, webpack, gulp) имеют возможность отслеживать изменения файлов, а затем выполнять автоматическую обработку. Иногда я задаюсь вопросом, как эти инструменты элегантно реализуют изменения файлов? Почему моя среда разработки находится в режиме наблюдения за некоторыми инструментами, процессор будет бешено разгоняться, но эти проблемы не возникнут, когда я сменю операционную систему? Из любопытства узнайте об изменениях файлов мониторинга NodeJs.детальА также некоторые существующие проблемы, чокидар сновакакРешить эти проблемы

Введение в чокидар

Что такое чокидар?

chokidar — это библиотека, которая инкапсулирует способность Node.js отслеживать изменения файлов файловой системы.

Встроенная функция мониторинга Node.js не проста в использовании? Зачем делать такую ​​инкапсуляцию?

Нативная функция мониторинга Node.js действительно проблематична.Согласно введению chokidar, существуют следующие проблемы:

Node.js fs.watch:

  • Изменения имени файла не отображаются в MacOS
  • О событиях не сообщается при использовании таких редакторов, как Sublime в MacOS.
  • Часто сообщают о двух инцидентах
  • Уведомлять о большинстве событий какrename
  • Нет удобного способа рекурсивно отслеживать дерево файлов

Node.js fs.watchFile:

  • Обработка событий имеет массу проблем
  • Не предоставляет функцию рекурсивного мониторинга файлового дерева.
  • вызвать высокую загрузку ЦП

chokidar решает вышеуказанные проблемы и был протестирован в большом количестве проектов с открытым исходным кодом и производственных средах.

Версия

3.1.0

Структура проекта

объяснять:

  • индекс: запись программы, включая основную логику программы, использование узла по умолчаниюfs.watchа такжеfs.watchFileКонтролируйте файловые ресурсы, если это система OS X, она будет проходить пользовательскийfsevents-handlerМониторинг файловых ресурсов
  • nodefs-handler: на основе nodejsfs.watchа такжеfs.watchFileМонитор ресурсов файла расширения интерфейса
  • fsevents-handler: самодельный монитор файловых ресурсов, который также использует модуль fs, но не использует интерфейсы watch и watchFile

ключевой процесс

index:

  1. Логика входа:
/**
 * Instantiates watcher with paths to be tracked.
 * @param {String|Array<String>} paths file/directory paths and/or globs
 * @param {Object=} options chokidar opts
 * @returns an instance of FSWatcher for chaining.
 */
const watch = (paths, options) => {
  const watcher = new FSWatcher(options);
  watcher.add(watcher._normalizePaths(paths));
  return watcher;
};

exports.watch = watch;
const chokidar = require('chokidar');

// One-liner for current directory
chokidar.watch('.').on('all', (event, path) => {
  console.log(event, path);
});

Предоставьте метод наблюдения внешнему миру, метод наблюдения создаст экземпляр FSWatcher, отформатирует (преобразует в массив) входные пути пути мониторинга и передаст их экземпляру FSWatcher для мониторинга.

  1. Процесс создания экземпляра FSWatcher
/**
 * Watches files & directories for changes. Emitted events:
 * `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
 *
 *     new FSWatcher()
 *       .add(directories)
 *       .on('add', path => log('File', path, 'was added'))
 */
class FSWatcher extends EventEmitter
this._emitRaw = (...args) => this.emit('raw', ...args);
this._readyEmitted = false;
this.options = opts;
// Initialize with proper watcher.
  if (opts.useFsEvents) {
    this._fsEventsHandler = new FsEventsHandler(this);
  } else {
    this._nodeFsHandler = new NodeFsHandler(this);
  }

После обработки параметров конфигурации ключевым моментом является решение, использовать ли FsEventsHandler или NodeFsHandler в зависимости от конечной ситуации.

Поскольку FSWatcher расширяется от EventEmitter, экземпляр FSWatcher имеет методы on и emit для реализации генерации и мониторинга событий, а метод _emitRaw передается в два экземпляра обработчика, чтобы обработчик мог получить возможность генерировать события наружу.

  1. Ключевой метод: добавить
/**
 * Adds paths to be watched on an existing FSWatcher instance
 * @param {Path|Array<Path>} paths_
 * @param {String=} _origAdd private; for handling non-existent paths to be watched
 * @param {Boolean=} _internal private; indicates a non-user add
 * @returns {FSWatcher} for chaining
 */
add(paths_, _origAdd, _internal) {
  const {cwd, disableGlobbing} = this.options;
  this.closed = false;

  if (this.options.useFsEvents && this._fsEventsHandler) {
    if (!this._readyCount) this._readyCount = paths.length;
    if (this.options.persistent) this._readyCount *= 2;
    paths.forEach((path) => this._fsEventsHandler._addToFsEvents(path));
  } else {
    if (!this._readyCount) this._readyCount = 0;
    this._readyCount += paths.length;
    Promise.all(
      paths.map(async path => {
        const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd);
        if (res) this._emitReady();
        return res;
      })
    ).then(results => {
      if (this.closed) return;
      results.filter(item => item).forEach(item => {
        this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
      });
    });
  }

Пройдите пути и отслеживайте состояние файла через fsEventsHandler или nodeFsHandler в соответствии с условиями.

nodedef-handler

Из логики индекса мы можем знать, что ключевой метод ввода этого модуля — _addToNodeFs.

/**
 * Handle added file, directory, or glob pattern.
 * Delegates call to _handleFile / _handleDir after checks.
 * @param {String} path to file or ir
 * @param {Boolean} initialAdd was the file added at watch instantiation?
 * @param {Object} priorWh depth relative to user-supplied path
 * @param {Number} depth Child path actually targetted for watch
 * @param {String=} target Child path actually targeted for watch
 * @returns {Promise}
 */
async _addToNodeFs(path, initialAdd, priorWh, depth, target) {
  const ready = this.fsw._emitReady;
  if (this.fsw._isIgnored(path) || this.fsw.closed) {
    ready();
    return false;
  }

  let wh = this.fsw._getWatchHelpers(path, depth);
  if (!wh.hasGlob && priorWh) {
    wh.hasGlob = priorWh.hasGlob;
    wh.globFilter = priorWh.globFilter;
    wh.filterPath = entry => priorWh.filterPath(entry);
    wh.filterDir = entry => priorWh.filterDir(entry);
  }

Основная логика этого метода заключается в следующем:

if (stats.isDirectory()) {
      const targetPath = follow ? await fsrealpath(path) : path;
      closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
      // preserve this symlink's target path
      if (path !== targetPath && targetPath !== undefined) {
        this.fsw._symlinkPaths.set(targetPath, true);
      }
    } else if (stats.isSymbolicLink()) {
      const targetPath = follow ? await fsrealpath(path) : path;
      const parent = sysPath.dirname(wh.watchPath);
      this.fsw._getWatchedDir(parent).add(wh.watchPath);
      this.fsw._emit('add', wh.watchPath, stats);
      closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath);

      // preserve this symlink's target path
      if (targetPath !== undefined) {
        this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
      }
    } else {
      closer = this._handleFile(wh.watchPath, stats, initialAdd);
    }

Видно, что здесь задействованы два важных метода: _handleDir и _handleFile.

_handleFile обрабатывает определенные пути к файлам

_handleDir обрабатывает путь к папке

Прочитав их исходный код, они в конечном итоге приведут к методу: _watchWithNodeFs.

/**
 * Watch file for changes with fs_watchFile or fs_watch.
 * @param {String} path to file or dir
 * @param {Function} listener on fs change
 * @returns {Function} closer for the watcher instance
 */
_watchWithNodeFs(path, listener) {
  // createFsWatchInstance
  // setFsWatchFileListener

Абстрактный процесс выглядит следующим образом:

Рекурсивно обходя каталог, вызываяfs.watchFileа такжеfs.watchДва метода генерируют прослушиватели и управляют ими для обеспечения эффективного мониторинга файлов и каталогов.

fsevent-handler

Главный вход_addToFsEvents

Абстрактная структура выглядит следующим образом:

Как видите, ключевым моментом является вызов fsevents.watch.

Модуль fsevents исходит из сторонних зависимостей:

 "engines": {
    "node": ">= 8"
  },
  "dependencies": {
    "anymatch": "^3.1.0",
    "braces": "^3.0.2",
    "glob-parent": "^5.0.0",
    "is-binary-path": "^2.1.0",
    "is-glob": "^4.0.1",
    "normalize-path": "^3.0.0",
    "readdirp": "^3.1.1"
  },
  "optionalDependencies": {
    "fsevents": "^2.0.6"
  },

Ридми fsevents на github описывается как:

Видно, что модуль fs-events является модулем расширения nodejs, который вызывает базовый API MacOS и соответствующие события мониторинга файлов, тем самым избегая проблемы мониторинга самого модуля fs nodejs.

Суммировать

  1. Следует сказать, что в коде chokidar еще есть много возможностей для инженерных улучшений, и он должен быть написан более лаконично, с меньшим связыванием модулей и лучшими методами, именами переменных и т. д.;
  2. Благодаря этому анализу мы можем понять общую структуру модуля chokidar и узнать источники событий мониторинга в разных средах; есть еще много деталей: фильтрация событий, объединение событий, изменения в слушателях и связанный контент будет продолжать обновляться. ;