Node.js 定制 REPL 的妙用

老雷 创作于 2015-10-31
全文约 2323 字,预计阅读时间为 0 时 12 分钟

相信在学习 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 的方法并不高深,如果在合适的场景中使用,却也能省不少事情。我目前能想到的应用场景有以下几个:

相关链接

关于作者

老雷 : Web开发者、 一登后端架构师、 《Node.js实战》作者之一
个人主页: http://ucdok.com
GitHub: https://github.com/leizongmin
知识共享许可协议
本作品由 老雷 创作,采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。