;

Node.js定制REPL的妙用

相信在学习Node.js的时候,对Node.js的REPL并不陌生。我们可以在REPL里面输入JavaScript代码并立刻看到效果,常常用来试用一些新的模块,甚至直接把REPL当计算器来用。

最近在修改以前使用Node.js写的中文分词模块时,想要看到代码修改后的效果,但是又不方便马上写测试代码,自然想到使用REPL来测试。比如执行以下命令启动Node.js的REPL界面:

$ node

然后在控制台界面中输入要测试的代码(其中>开头的行是手工输入并按回车的代码,其他部分为REPL的输出结果):

> var Segment = require('./')
undefined
> var s = new Segment()
undefined
> s.useDefault(); 1
1
> s.doSegment('神奇的REPL')
[ { w: '神奇的', p: 1073741824 }, { w: 'REPL', p: 16 } ]
> 

但当我修改了模块的代码后,要看效果时又要重复输入上面的代码,这种做重复无意义工作的行为绝非是一名有理想的程序员想要的。于是,我决定自己定制一个REPL,这样就可以预先执行一些初始化代码,一启动程序就可以进入主题了。

看了一下REPL模块的文档之后,大概搞清了怎么个用法,接下来开始写代码了。

首先在项目的根目录下新建名为repl的文件,代码如下:

#!/usr/bin/env node

var repl = require('repl');

// 创建一个REPL
var r = repl.start('> ');
// context即为REPL中的上下文环境
var c = r.context;

// 测试用的初始化代码
// 在REPL中可以通过Segment和segment来访问以下两个变量
c.Segment = require('./');
c.segment = new c.Segment();
c.segment.useDefault();

// 精简函数名,方便手工输入,在REPL中可以通过s来访问此函数
c.s = function () {
  return c.segment.doSegment.apply(c.segment, arguments);
};

文件第一行的#!/usr/bin/env node表示这是一个脚本文件,使用node命令来执行它,所以还要给这个文件加上可执行权限:

$ chmod +x repl

现在就可以试试这个定制的REPL了:

$ ./repl
> s('神奇的REPL')
[ { w: '神奇的', p: 1073741824 }, { w: 'REPL', p: 16 } ]
> 

之后每次更改了代码,只要按两下CTRL+C来退出当前REPL,再执行./repl来启动程序,然后输入s('神奇的REPL')就可以看到分词的效果了,如果要执行其他函数,也可以直接操作segment变量来做。

但是,一名有理想的程序员绝不会满足于此的。

当我修改了模块代码,为什么要重启REPL呢,难道不能重新加载一次这个模块,然后该干嘛还干嘛?

Node.js的模块系统文档可知,在使用require()来加载模块后,相关的文件内容会被缓存到require.cache[filename]中,当再次require()此文件的时候并不会重新加载。所以要想在不重启进程的情况下重新加载模块,我们就要清理这个模块相关的所有缓存。

repl文件改成以下代码:

#!/usr/bin/env node

var fs = require('fs');
var path = require('path');
var repl = require('repl');


var r = repl.start('> ');
var c = r.context;

// 原来的初始化代码放到此函数内
c._load = function () {
  c.Segment = require('./');
  c.segment = new c.Segment();
  c.segment.useDefault();
  c.s = function () {
    return c.segment.doSegment.apply(c.segment, arguments);
  };
};

// 在REPL中执行reload()可重新加载模块
c.reload = function () {
  var t = Date.now();
  
  // 清空当前项目根目录下所有文件的缓存
  var dir = path.resolve(__dirname) + path.sep;
  for (var i in require.cache) {
    if (i.indexOf(dir) === 0) {
      delete require.cache[i];
    }
  }
  
  // 重新执行初始化
  c._load();
  console.log('OK. (spent %sms)', Date.now() - t);
}

c._load();

好了,在修改了模块的代码后,只要在REPL中执行reload()函数就能重新载入最新的代码了:

> reload()
OK. (spent 458ms)
undefined
> s('神奇的REPL')
[ { w: '神奇的', p: 1073741824 }, { w: 'REPL', p: 16 } ]
> 

总结

本文所介绍的定制REPL的方法并不高深,如果在合适的场景中使用,却也能省不少事情。我目前能想到的应用场景有以下几个:

参考文献


原文链接:http://morning.work/page/2015-10/node_repl_module.html 转载请注明出处