NodeJS
安装 nodejs 客户端
$ npm install fb-watchman
以及导入并创建一个客户端实例
var watchman = require('fb-watchman');
var client = new watchman.Client();
本文档假设您正在使用发布到 npm 仓库的最新可用版本的 fb-watchman
包。
检查 watchman 是否可用
客户端可以安装,而无需安装服务。重要的是要处理不可用性,并测试已安装的服务是否支持您的应用程序所需的功能。
capabilityCheck
方法发出一个 version 命令来查询服务器的功能。
var watchman = require('fb-watchman');
var client = new watchman.Client();
client.capabilityCheck({optional:[], required:['relative_root']},
function (error, resp) {
if (error) {
// error will be an Error object if the watchman service is not
// installed, or if any of the names listed in the `required`
// array are not supported by the server
console.error(error);
}
// resp will be an extended version response:
// {'version': '3.8.0', 'capabilities': {'relative_root': true}}
console.log(resp);
});
启动监视
watchman 中的几乎所有操作都围绕监视目录树展开。您可以重复请求监视同一目录而不会出错;watchman 将重用现有的监视。
var watchman = require('fb-watchman');
var client = new watchman.Client();
var dir_of_interest = "/some/path";
client.capabilityCheck({optional:[], required:['relative_root']},
function (error, resp) {
if (error) {
console.log(error);
client.end();
return;
}
// Initiate the watch
client.command(['watch-project', dir_of_interest],
function (error, resp) {
if (error) {
console.error('Error initiating watch:', error);
return;
}
// It is considered to be best practice to show any 'warning' or
// 'error' information to the user, as it may suggest steps
// for remediation
if ('warning' in resp) {
console.log('warning: ', resp.warning);
}
// `watch-project` can consolidate the watch for your
// dir_of_interest with another watch at a higher level in the
// tree, so it is very important to record the `relative_path`
// returned in resp
console.log('watch established on ', resp.watch,
' relative_path', resp.relative_path);
});
});
订阅更改
大多数 node 应用程序都对订阅实时文件更改通知感兴趣。在 watchman 中,这些通过发出 subscribe 命令来配置。订阅在您的客户端连接期间有效,或者直到您使用 unsubscribe 命令取消订阅为止。
以下代码将为树中所有匹配查询表达式的文件生成订阅结果,然后在文件更改时生成订阅结果
// `watch` is obtained from `resp.watch` in the `watch-project` response.
// `relative_path` is obtained from `resp.relative_path` in the
// `watch-project` response.
function make_subscription(client, watch, relative_path) {
sub = {
// Match any `.js` file in the dir_of_interest
expression: ["allof", ["match", "*.js"]],
// Which fields we're interested in
fields: ["name", "size", "mtime_ms", "exists", "type"]
};
if (relative_path) {
sub.relative_root = relative_path;
}
client.command(['subscribe', watch, 'mysubscription', sub],
function (error, resp) {
if (error) {
// Probably an error in the subscription criteria
console.error('failed to subscribe: ', error);
return;
}
console.log('subscription ' + resp.subscribe + ' established');
});
// Subscription results are emitted via the subscription event.
// Note that this emits for all subscriptions. If you have
// subscriptions with different `fields` you will need to check
// the subscription name and handle the differing data accordingly.
// `resp` looks like this in practice:
//
// { root: '/private/tmp/foo',
// subscription: 'mysubscription',
// files: [ { name: 'node_modules/fb-watchman/index.js',
// size: 4768,
// exists: true,
// type: 'f' } ] }
client.on('subscription', function (resp) {
if (resp.subscription !== 'mysubscription') return;
resp.files.forEach(function (file) {
// convert Int64 instance to javascript integer
const mtime_ms = +file.mtime_ms;
console.log('file changed: ' + file.name, mtime_ms);
});
});
}
仅订阅已更改的文件
上面的示例将在建立订阅时为现有(和已删除!)文件生成结果。在某些应用程序中,这可能是不可取的。以下示例展示了如何添加逻辑时间约束。
watchman 使用抽象时钟跟踪更改。我们将在启动监视时确定当前时钟,然后将其作为约束添加到我们的订阅中。
function make_time_constrained_subscription(client, watch, relative_path) {
client.command(['clock', watch], function (error, resp) {
if (error) {
console.error('Failed to query clock:', error);
return;
}
sub = {
// Match any `.js` file in the dir_of_interest
expression: ["allof", ["match", "*.js"]],
// Which fields we're interested in
fields: ["name", "size", "exists", "type"],
// add our time constraint
since: resp.clock
};
if (relative_path) {
sub.relative_root = relative_path;
}
client.command(['subscribe', watch, 'mysubscription', sub],
function (error, resp) {
// handle the result here
});
});
}
NodeJS API 参考
方法
client.capabilityCheck(options, done)
capabilityCheck
方法发出一个 version 命令来查询服务器的功能。
如果服务器不支持功能,capabilityCheck
将基于服务器报告的版本,为一些重要的功能模拟功能响应。
options
参数可能包含以下属性
optional
列出可选功能名称的数组required
列出必需功能名称的数组
这些属性将传递给底层的 version
命令。
done
参数是一个回调函数,命令完成时将传递 (error, result)。发出 capabilityCheck
调用而不提供 done
回调是没有意义的。
响应对象将包含一个 capabilities
对象属性,其键将是 optional
和 required
功能名称的并集,其值将根据功能名称的可用性为 true
或 false
。
如果服务器不支持任何 required
功能,则 done
回调中的 error
参数将被设置,并将包含有意义的错误消息。
client.capabilityCheck({optional:[], required:['relative_root']},
function (error, resp) {
if (error) {
// error will be an Error object if the watchman service is not
// installed, or if any of the names listed in the `required`
// array are not supported by the server
console.error(error);
}
// resp will be an extended version response:
// {'version': '3.8.0', 'capabilities': {'relative_root': true}}
console.log(resp);
});
client.command(args[, done])
向 watchman 服务发送命令。 args
是一个数组,用于指定命令名称和任何可选参数。命令被排队并异步分派。您可以将多个命令排队到服务;一旦客户端连接建立,它们将以 FIFO 顺序分派。
done
参数是一个回调函数,命令完成时将传递 (error, result)。如果您对命令的结果不感兴趣,可以省略它。
client.command(['watch-project', process.cwd()], function(error, resp) {
if (error) {
console.log('watch failed: ', error);
return;
}
if ('warning' in resp) {
console.log('warning: ', resp.warning);
}
if ('relative_path' in resp) {
// We will need to remember and adjust for relative_path
console.log('watching project ', resp.watch, ' relative path to cwd is ',
resp.relative_path);
} else {
console.log('watching ', resp.watch);
}
});
如果 resp
中存在名为 warning
的字段,则 watchman 服务正在尝试传达用户应该看到和解决的问题。例如,如果系统监视资源需要调整,watchman 将提供有关此问题以及如何补救的信息。建议构建在此库之上的工具将警告消息传递给用户。
client.end()
终止与 watchman 服务的连接。不会等待任何排队的命令发送。
事件
watchman 客户端对象发出以下事件
事件: 'connect'
当客户端成功连接到 watchman 服务时发出
事件: 'error'
当与 watchman 服务的套接字遇到错误时发出。
如果无法成功执行 watchman CLI 二进制文件以确定如何与服务器进程通信,也可能在建立连接之前发出。
它传递一个封装错误的变量。
事件: 'end'
当与 watchman 服务的套接字关闭时发出
事件: 'log'
响应来自 watchman 服务的单方面 log
PDU 发出。要启用这些,您需要向服务发送 log-level
命令
// This is very verbose, you probably don't want to do this
client.command(['log-level', 'debug']);
client.on('log', function(info) {
console.log(info);
});
事件: 'subscription'
响应来自 watchman 服务的单方面 subscription
PDU 发出。要启用这些,您需要向服务发送 subscribe
命令
// Subscribe to notifications about .js files
client.command(['subscribe', process.cwd(), 'mysubscription', {
expression: ["match", "*.js"]
}],
function(error, resp) {
if (error) {
// Probably an error in the subscription criteria
console.log('failed to subscribe: ', error);
return;
}
console.log('subscription ' + resp.subscribe + ' established');
}
);
// Subscription results are emitted via the subscription event.
// Note that watchman will deliver a list of all current files
// when you first subscribe, so you don't need to walk the tree
// for yourself on startup
client.on('subscription', function(resp) {
console.log(resp.root, resp.subscription, resp.files);
});
要取消订阅,请使用 unsubscribe
命令并传入要取消的订阅的名称
client.command(['unsubscribe', process.cwd(), 'mysubscription']);
请注意,订阅名称的作用域限定为与 watchman 服务的连接;多个不同的客户端可以使用相同的订阅名称,而不必担心发生冲突。