跳到主要内容

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 对象属性,其键将是 optionalrequired 功能名称的并集,其值将根据功能名称的可用性为 truefalse

如果服务器不支持任何 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 服务的连接;多个不同的客户端可以使用相同的订阅名称,而不必担心发生冲突。