剩下一個watch模塊,這個模塊比較深,先大概過一下整體涉及內容再分部講解。 流程圖如下: NodeWatchFileSystem 除去細節代碼,該模塊大體如下; 1、引入Watchpack模塊 2、接受一個inputFileSystem作為構造函數的參數 3、根據配置選項實例化一個Watchpac ...
剩下一個watch模塊,這個模塊比較深,先大概過一下整體涉及內容再分部講解。
流程圖如下:
NodeWatchFileSystem
const Watchpack = require("watchpack"); class NodeWatchFileSystem { constructor(inputFileSystem) { this.inputFileSystem = inputFileSystem; this.watcherOptions = { aggregateTimeout: 0 }; this.watcher = new Watchpack(this.watcherOptions); } watch( files, /*Array*/ dirs, /*Array*/ missing, /*Array*/ startTime, /*number*/ options, /*object*/ callback, /*function*/ callbackUndelayed /*function*/ ) { // params validate... const oldWatcher = this.watcher; // 生成Watchpack對象 this.watcher = new Watchpack(options); if (callbackUndelayed) this.watcher.once("change", callbackUndelayed); this.watcher.once("aggregated", (changes, removals) => { /**/ }); // 調用watch方法 this.watcher.watch(files.concat(missing), dirs.concat(missing), startTime); if (oldWatcher) { oldWatcher.close(); } return { close: () => { /**/ }, pause: () => { /**/ } }; } } module.exports = NodeWatchFileSystem;
除去細節代碼,該模塊大體如下;
1、引入Watchpack模塊
2、接受一個inputFileSystem作為構造函數的參數
3、根據配置選項實例化一個Watchpack類
4、核心watch方法為調用實例類的watch方法,傳入給定參數,綁定兩個一次性事件綁定並返回了一個對象
模塊核心的方法調用的是Watchpack實體類上的,所以需要進一步探究該類。
該模塊涉及到了nodejs的event模塊,內容非常簡單,這裡就不做介紹了,詳情可查看官網API:https://nodejs.org/dist/latest-v8.x/docs/api/events.html
Watchpack
var watcherManager = require("./watcherManager"); var EventEmitter = require("events").EventEmitter; Watchpack.prototype = Object.create(EventEmitter.prototype); class Watchpack { constructor(options) { EventEmitter.call(this); if (!options) options = {}; if (!options.aggregateTimeout) options.aggregateTimeout = 200; this.options = options; this.watcherOptions = { ignored: options.ignored, poll: options.poll }; this.fileWatchers = []; this.dirWatchers = []; this.mtimes = Object.create(null); this.paused = false; this.aggregatedChanges = []; this.aggregatedRemovals = []; this.aggregateTimeout = 0; this._onTimeout = this._onTimeout.bind(this); } watch(files, directories, startTime) { this.paused = false; var oldFileWatchers = this.fileWatchers; var oldDirWatchers = this.dirWatchers; this.fileWatchers = files.map(function(file) { return this._fileWatcher(file, watcherManager.watchFile(file, this.watcherOptions, startTime)); }, this); this.dirWatchers = directories.map(function(dir) { return this._dirWatcher(dir, watcherManager.watchDirectory(dir, this.watcherOptions, startTime)); }, this); oldFileWatchers.forEach(function(w) { w.close(); }, this); oldDirWatchers.forEach(function(w) { w.close(); }, this); }; pause() { /**/ }; getTimes() { /**/ }; _fileWatcher(file, watcher) { /**/ }; _dirWatcher(item, watcher) { /**/ }; _onChange(item, mtime, file) { /**/ }; _onRemove(item, file) { /**/ }; _onTimeout() { /**/ }; close() { /**/ }; } module.exports = Watchpack; function addWatchersToArray(watchers, array) { /**/ }
本模塊引入了並繼承了nodejs的EventEmitter,並引入了新模塊watcherManager,主要內容羅列如下:
1、構造函數接受一個對象,鍵包括aggregateTimeout、ignored、poll,本例只傳入第一個並設置為0
2、核心方法為watch,依賴於引入的watchManager模塊
3、其餘方法均為工具方法
WatcherManager
var path = require("path"); class WatcherManager { constructor() { this.directoryWatchers = {}; }; // 工廠函數 getDirectoryWatcher(directory, options) { // 引入模塊 var DirectoryWatcher = require("./DirectoryWatcher"); options = options || {}; var key = directory + " " + JSON.stringify(options); if (!this.directoryWatchers[key]) { this.directoryWatchers[key] = new DirectoryWatcher(directory, options); // 文件監視結束則從容器刪除 this.directoryWatchers[key].on("closed", function() { delete this.directoryWatchers[key]; }.bind(this)); } return this.directoryWatchers[key]; }; // 監視文件 watchFile(p, options, startTime) { var directory = path.dirname(p); return this.getDirectoryWatcher(directory, options).watch(p, startTime); }; // 監視目錄 watchDirectory(directory, options, startTime) { return this.getDirectoryWatcher(directory, options).watch(directory, startTime); }; } module.exports = new WatcherManager();
可以看出這是一個中間處理函數,其中構造函數生成了一個容器,容器的鍵為目錄+參數生成的一個字元串,當監視關閉後會並立即刪除。
這個模塊類似於tapable,是一個監視對象管理器。
然後是監視核心實現模塊,模塊內容比較多,這裡只簡單看一下構造函數以及watch方法:
var EventEmitter = require("events").EventEmitter; var async = require("async"); var chokidar = require("chokidar"); var fs = require("graceful-fs"); class Watcher { constructor(directoryWatcher, filePath, startTime) { EventEmitter.call(this); this.directoryWatcher = directoryWatcher; this.path = filePath; this.startTime = startTime && +startTime; this.data = 0; }; checkStartTime(mtime, initial) { /**/ }; close() { /**/ }; } function DirectoryWatcher(directoryPath, options) { EventEmitter.call(this); this.options = options; this.path = directoryPath; this.files = Object.create(null); this.directories = Object.create(null); this.watcher = chokidar.watch(directoryPath, { ignoreInitial: true, persistent: true, followSymlinks: false, depth: 0, atomic: false, alwaysStat: true, ignorePermissionErrors: true, ignored: options.ignored, usePolling: options.poll ? true : undefined, interval: typeof options.poll === "number" ? options.poll : undefined, disableGlobbing: true }); this.watcher.on("add", this.onFileAdded.bind(this)); this.watcher.on("addDir", this.onDirectoryAdded.bind(this)); this.watcher.on("change", this.onChange.bind(this)); this.watcher.on("unlink", this.onFileUnlinked.bind(this)); this.watcher.on("unlinkDir", this.onDirectoryUnlinked.bind(this)); this.watcher.on("error", this.onWatcherError.bind(this)); // ... } DirectoryWatcher.prototype.watch = function watch(filePath, startTime) { this.watchers[withoutCase(filePath)] = this.watchers[withoutCase(filePath)] || []; this.refs++; var watcher = new Watcher(this, filePath, startTime); watcher.on("closed", function() { /**/ }.bind(this)); // ... return watcher; }; // ... module.exports = DirectoryWatcher;
從構造函數和模塊引入可以得到很多信息,如下:
1、引入了graceful-js模塊,可以看出底層還是利用nodejs的fs模塊來進行監視
2、所有的監視事件都是基於nodejs的EventEmitter模塊來進行操作
3、內部還有一個輔助類Watcher
4、根據構造函數的代碼,監視的操作包含(可能不限於)新增文件、新增文件夾、改變內容、刪除文件、刪除文件夾等
async模塊是一個類似於tapable的輔助工具,用於非同步處理批量方法,詳細內容可自行去網上查閱。
構造函數中,該模塊又再次引用了chokidar模塊,並調用其watch方法進行初始化,看似調用方法,源碼簡化後如下:
class FSWatcher { // ... } exports.FSWatcher = FSWatcher; exports.watch = function(paths, options) { return new FSWatcher(options).add(paths); };
假的,這還是個new操作,只是為了方便把兩步合成到了一個方法中。
所有的模塊整理如上,下麵幾節再來剖析每一塊內容。