node-watch是对上面的fs.watch
的封装和增强。它解决了以下问题:
- 编辑器会生成临时的文件,导致回调函数会被触发两次
- 在观察单个文件保存时,回调函数只会触发一次
- 解决Linux和旧版本node不支持递归的问题
使用方法如下:
const rollup = require("rollup");
const watch = require("node-watch");
rollup
.rollup({
input: "main.js",
})
.then(async (bundle) => {
await bundle.write({
file: "bundle.js",
});
// 在rollup打包之后,监听项目文件
let watcher = watch("./", { recursive: true });
watcher.on("change", function (evt, name) {
console.log("更新了~~~", name);
});
});
每次保存文件都会触发更新,不论文件内容是否有变更。
思路
执行
const watch = require("node-watch");
let watcher = watch("./", { recursive: true });
这个监听就启动了,根据源码入口找到了lib/watch.js
文件,从中找到watch
函数,核心代码如下:
function watch(fpath, options, fn) {
var watcher = new Watcher(); // 实例一个事件触发器
// 省略一些代码,主要是负责检查传入的fpath类型是否正确,文件是否存在
// 是数组,则递归观察文件树
if (is.array(fpath)) {
if (fpath.length === 1) {
return watch(fpath[0], options, fn);
}
var filterDups = createDupsFilter();
return composeWatcher(unique(fpath).map(function(f) { // unique过滤不需要监听的文件
var w = watch(f, options); // 递归
if (is.func(fn)) {
w.on('change', filterDups(fn));
}
return w;
}));
}
// 监听文件
if (is.file(fpath)) {
watcher.watchFile(fpath, options, fn);
emitReady(watcher);
}
// 监听目录
else if (is.directory(fpath)) {
var counter = semaphore(function () {
emitReady(watcher);
});
watcher.watchDirectory(fpath, options, fn, counter);
}
return watcher.expose();
}
一开始实例一个Watcher
事件触发器,后面则是根据这个实例,注册所有的事件,我们看看Watcher
构造函数做了什么工作。
const events = require("events")
const util = require("util")
// 构造函数
function Watcher() {
events.EventEmitter.call(this);
this.watchers = {};
this._isReady = false;
this._isClosed = false;
}
util.inherits(Watcher, events.EventEmitter);
Watcher.prototype.expose = function(){/* do something */}
Watcher.prototype.add = function(){/* do something */}
// 监听文件
Watcher.prototype.watchFile = function(){
// 核心代码
var watcher = fs.watch(parent, opts);
this.add(watcher, {
type: 'file',
fpath: parent,
options: Object.assign({}, opts, {
encoding: options.encoding
}),
compareName: function(n) {
return is.samePath(n, file);
}
});
if (is.func(fn)) {
if (fn.length === 1) deprecationWarning(); // 解决回调两次的问题
this.on('change', fn);
}
}
// 监听文件夹
Watcher.prototype.watchDirectory = function(file, options, fn){
// 兼容linux和旧版本
hasNativeRecursive(function(has) {
options.recursive = !!options.recursive;
// 核心代码
var watcher = fs.watch(dir, opts);
self.add(watcher, {
type: 'dir',
fpath: dir,
options: options
});
if (is.func(fn)) {
if (fn.length === 1) deprecationWarning(); // 解决回调两次的问题
self.on('change', fn);
}
if (options.recursive && !has) {
getSubDirectories(dir, function(d) {
if (shouldNotSkip(d, options.filter)) { // 过滤需要忽略的文件
self.watchDirectory(d, options, null, counter); // 递归
}
}, counter());
}
});
}
简单概括就是继承了EventEmitter
的属性,实现了文件、文件夹的监听事件。
小结
- 执行
watch
会创建一个events事件触发器,其中主要是继承了EventEmitter
类。 - 在继承的基础上重写了
watchFile
和watchDirectory
函数,实现了文件和文件夹的监听事件。 -
watch
支持数组,遇到数组使用递归进行处理。 - 通过判断
fn
调用的次数来解决元素fs.watch
回调被多次调用的问题,只有调用次数为1时才执行回调。 -
hasNativeRecursive
函数负责解决linux和旧版本Node递归的问题,解决思路是根据不同环境动态创建临时文件或者文件夹实现当前环境所支持的监听事件。文件监听依旧使用的是fs.watch
。当监听结束之后会自动把临时文件清除。
根据对源码的解读,能够大体了解封装的思路,以及如何解决原生遗留的问题。