.12-浅析webpack源码之NodeWatchFileSystem模块总览
剩下一个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操作,只是为了方便把两步合成到了一个方法中。
所有的模块整理如上,下面几节再来剖析每一块内容。