Цель
Многие инструменты (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: на основе nodejs
fs.watch
а такжеfs.watchFile
Монитор ресурсов файла расширения интерфейса - fsevents-handler: самодельный монитор файловых ресурсов, который также использует модуль fs, но не использует интерфейсы watch и watchFile
ключевой процесс
index:
- Логика входа:
/**
* 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 для мониторинга.
- Процесс создания экземпляра 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 передается в два экземпляра обработчика, чтобы обработчик мог получить возможность генерировать события наружу.
- Ключевой метод: добавить
/**
* 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.
Суммировать
- Следует сказать, что в коде chokidar еще есть много возможностей для инженерных улучшений, и он должен быть написан более лаконично, с меньшим связыванием модулей и лучшими методами, именами переменных и т. д.;
- Благодаря этому анализу мы можем понять общую структуру модуля chokidar и узнать источники событий мониторинга в разных средах; есть еще много деталей: фильтрация событий, объединение событий, изменения в слушателях и связанный контент будет продолжать обновляться. ;