剩下一個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操作,只是為了方便把兩步合成到了一個方法中。
?
所有的模塊整理如上,下面幾節再來剖析每一塊內容。