文件(夹)监听---Node原生 VS chokidar

Node原生 VS chokidar

Posted by Ray on 2018-01-25

前言

最近在看vue-hackernews-2.0代码的时候看到在build配置中用了一个chokidar的文件监听模块,一时好奇为什么不用fs.watch的原生操作,忽然想到确实对于监听文件方面确实只是停留在配置gulp监听文件修改后自动转换的gulp.watch,以及简单配置已经高集成的webpack,似乎平时并没有机会经常用Node“干净”的再去做监控操作。所以这篇文章就是建立在介绍使用node原生监听文件方式以及被类似于gulp和webpack也在使用的第三方模块“chokidar”,以及对比使用这两个同样功能的感受。

Node原生

首先当然是先介绍Node对文件(夹)监听的原生方法,下面是我的测试demo的文件结构

Node对文件或者文件夹的监听,通过原生模块fs提供的watch()watchFile()方法,先来看看他们的用法。

fs.watch(filename[, options][, listener])

其中filename可以是一个文件夹也可以可以使一个文件,

对于第二个options参数,他如果是一个字符串,那么就应该指定用于传给监听器的文件名的字符编码(encoding),默认是”utf-8”

如果要定义其余的选项,就要把它定义成一个object

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs')

fs.watch('./testFolder',
{encoding:'utf-8',
recursive:false,
persistent:false
},
(eventType, filename)=>{
console.log(`${eventType}`)
console.log(`${filename}`)
});

其中persistent:true(默认为true)表示如果文件正在被监视,进程就继续运行不会跳出;recursive:false(默认值为false)表示所有的子目录都不会被监视.

最后的第三个参数,也就是listener回调函数如果执行,就会传入两个参数eventTypechangerename这两个值,分别对应着改变文件内容和文件重命名。

上述代码,当我改变testFolder/childFolder中的child.txt的内容时,函数内的回调将不会被执行,如果把recursive改成true则会打印:

1
2
change
childFolder/child.txt

这里需要注意的是当我删除掉目录下的test.txt时 ‘rename’ 这个事件类型也会被触发!,又重新添加回来这个文件’rename’也会触发,往里面写与之前相同的东西保存又会触发’rename’,确实有点不明确的坑。

而且在Node8.9.4的官方文档中说明原生的这个方法其他不足的地方

The fs.watch API is not 100% consistent across platforms, and is > unavailable in some situations.
The recursive option is only supported on macOS and Windows.

Providing filename argument in the callback is only supported on Linux, macOS, Windows, and AIX. Even on supported platforms, filename is not always guaranteed to be provided. Therefore, don’t assume that filename argument is always provided in the callback, and have some fallback logic if it is null.

就是说fs.watch API 不是 100% 跨平台一致的,且在某些情况下不可用。
而且recursive递归监视子目录选项只支持 macOS 和 Windows。并且listener回调中提供的 filename 参数仅在 Linux、macOS、Windows、以及 AIX 系统上支持。 即使在支持的平台中,filename 也不能保证提供。 因此,不要以为 filename 参数总是在回调中提供,如果它是空的,需要有一定的后备逻辑。为此官方文档上甚至给了你例子,生怕你信了这个filename,这也是下面会介绍的第三方模块chokidar文档上甚至“diss”了一下原生的一点。

1
2
3
4
5
6
7
8
fs.watch('somedir', (eventType, filename) => {
console.log(`event type is: ${eventType}`);
if (filename) {
console.log(`filename provided: ${filename}`);
} else {
console.log('filename not provided');
}
});

fs.watchFile(filename[, options], listener)

监视某一个文件

  • options是个对象,两个选项
    • persistent上面已经介绍过的功能,让进程处在监听状态不退出
    • interval轮询的时间单位是毫秒,表示被监听的文件每隔多久被轮一次,默认5001毫秒
1
2
3
4
fs.watchFile('./testFolder/test.txt', {interval: 1000}, (curr, prev)=>{
console.log(`${curr.ctime}`)
console.log(`${prev,ctime}`)
});

此时如果改变test.txt,会触发回调,并且会传入两个参数curr, prev这两个参数都是fs.Stats类的实例。curr表示当前状态,prev表示之前状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//fs.Stats类
Stats {
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atimeMs: 1318289051000.1,
mtimeMs: 1318289051000.1,
ctimeMs: 1318289051000.1,
birthtimeMs: 1318289051000.1,
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT }

其中atime是文件被访问的时间,mtime是修改时间,ctime表示改变时间

上述代码改变文件数据之后打印

1
2
Thu Jan 25 2018 21:16:25 GMT+0800 (CST)
Thu Jan 25 2018 21:16:23 GMT+0800 (CST)

官方提示watchFile方法效率差,尽可能别用。
总之就不得不说,类似于该文件被删除,然后又恢复这种情况,原生的状态和事件就是很不明确

第三方监视文件(夹)模块chokitar

先看一段(chorkitar)[https://www.npmjs.com/package/chokidar]在说明文档里的`diss`

一句Has a lot of other issues确实扎心,主要就是diss native事件处理的不好,不过我也确实试了一下也是这种感受

一句Chokidar resolves these problems.也很sao,加之很多知名项目正名,语法链式,也没什么说了。我们来看看这个模块是怎么用的。

API:

chokidar.watch(paths, [options])

  • 构造一个chokidar监听实例。

  • paths

    • 一个字符串或者是一个数组,描述监听的文件或者文件夹的路径
  • options
    • 对象数据类型,常用配置项:
      • persistent:bollean,与原生fs.watch一样,表示是否保护进程不退出持久监听,默认值为true
      • ignored:string,所要忽略监听的文件或者文件夹
      • ignoreInitial:bollean,表示是否对增加文件或者增加文件夹的时候进行发送事件,默认值为false表示add/addDir会触发事件
      • cwd:string类型,没有默认值,类似于appBasepath,监听的paths所相对的路径。
      • usePolling:bollean,表示是否使用前面提到的fs.watchFile()进行轮询操作,由于轮询会导致cpu飙升,所以此选项通常在需要通过网络监视文件的时候才设置为true即使用fs.watchFile(),默认值为false
      • depth:number类型,没有默认值,如果设定则表示限定了会递归监听多少个子目录。

eg:

1
2
3
4
5
6
7
8
9
10
const chokidar = require('chokidar')

chokidar.watch('testFolder', {
persistent: true,
ignored: /(^|[\/\\])\../,//忽略点文件
cwd: '.',//表示当前目录
depth:99//到位了....
}).on('all', (event, path) => {//监听除了ready, raw, and error之外所有的事件类型
console.log(event, path);
});

运行该代码之后:

表明在实例化监听对象的时候,因为默认ignoreInitial: false,chokidar会发emmit add/addDir事件,而此时要是在options对象中增加了ignoreInitial: true,表明忽略监听初始化追踪的栈事件,则在实例化得时候就不会捕捉到add/addDir事件了

此外,chokidar也提供了更加多的事件层次,通过更多的options配合做到对文件的更好的监控,其中支持的事件有:

事件名称 意义
add 新增文件时触发
addDir 新增文件夹的时候触发
unlink 对应的文件的删除
unlinkDir 对应的文件夹的删除
change 文件内容改变时触发
本文为原创文章作为学习交流笔记,如有错误请您评论指教
转载请注明来源:https://isliulei.com/article/Node-WatchFile/