REPL#

稳定性:2 - 稳定

源代码: lib/repl.js

node:repl 模块提供了一个 Read-Eval-Print-Loop (REPL) 实现,它既可以作为独立程序使用,也可以包含在其他应用程序中。可以使用以下方法访问它

const repl = require('node:repl'); 

设计和功能#

node:repl 模块导出 repl.REPLServer 类。在运行时,repl.REPLServer 的实例将接受用户输入的单个行,根据用户定义的评估函数对这些行进行评估,然后输出结果。输入和输出分别可以来自 stdinstdout,也可以连接到任何 Node.js

repl.REPLServer 的实例支持自动完成输入、完成预览、简单的 Emacs 风格行编辑、多行输入、ZSH 风格的逆向搜索、ZSH 风格的基于子字符串的历史搜索、ANSI 风格的输出、保存和恢复当前 REPL 会话状态、错误恢复以及可自定义的评估函数。不支持 ANSI 风格和 Emacs 风格行编辑的终端会自动回退到有限的功能集。

命令和特殊键#

所有 REPL 实例都支持以下特殊命令

  • .break: 在输入多行表达式时,输入 .break 命令(或按 Ctrl+C)来中止该表达式的进一步输入或处理。
  • .clear: 将 REPL context 重置为空对象,并清除任何正在输入的多行表达式。
  • .exit: 关闭 I/O 流,导致 REPL 退出。
  • .help: 显示此特殊命令列表。
  • .save: 将当前 REPL 会话保存到文件:> .save ./file/to/save.js
  • .load: 将文件加载到当前 REPL 会话中。> .load ./file/to/load.js
  • .editor: 进入编辑器模式(Ctrl+D 完成,Ctrl+C 取消)。
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
function welcome(name) {
  return `Hello ${name}!`;
}

welcome('Node.js User');

// ^D
'Hello Node.js User!'
> 

REPL 中的以下键组合具有这些特殊效果

  • Ctrl+C: 按一次时,与 .break 命令的效果相同。在空白行上按两次时,与 .exit 命令的效果相同。
  • Ctrl+D: 与 .exit 命令的效果相同。
  • Tab: 在空白行上按时,显示全局和局部(作用域)变量。在输入其他输入时按时,显示相关的自动完成选项。

有关与反向 i 搜索相关的键绑定,请参阅 reverse-i-search。有关所有其他键绑定,请参阅 TTY 键绑定

默认评估#

默认情况下,所有 repl.REPLServer 实例都使用一个评估函数,该函数评估 JavaScript 表达式并提供对 Node.js 内置模块的访问。这种默认行为可以通过在创建 repl.REPLServer 实例时传入一个替代评估函数来覆盖。

JavaScript 表达式#

默认评估器支持直接评估 JavaScript 表达式

> 1 + 1
2
> const m = 2
undefined
> m + 1
3 

除非在块或函数中另行限定范围,否则隐式声明或使用constletvar关键字声明的变量将在全局范围内声明。

全局和局部范围#

默认评估器可以访问全局范围内存在的任何变量。可以通过将变量分配给与每个REPLServer关联的context对象,显式地将变量暴露给REPL。

const repl = require('node:repl');
const msg = 'message';

repl.start('> ').context.m = msg; 

context对象中的属性在REPL中显示为局部属性。

$ node repl_test.js
> m
'message' 

默认情况下,上下文属性不是只读的。要指定只读全局变量,必须使用Object.defineProperty()定义上下文属性。

const repl = require('node:repl');
const msg = 'message';

const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
  configurable: false,
  enumerable: true,
  value: msg,
}); 
访问核心Node.js模块#

默认评估器将在使用时自动将Node.js核心模块加载到REPL环境中。例如,除非另行声明为全局变量或作用域变量,否则输入fs将按需评估为global.fs = require('node:fs')

> fs.createReadStream('./some/file'); 
全局未捕获异常#

REPL使用domain模块来捕获该REPL会话的所有未捕获异常。

REPL中使用domain模块会产生以下副作用

_(下划线)变量的赋值#

默认情况下,默认评估器会将最近评估的表达式的结果分配给特殊变量_(下划线)。显式地将_设置为一个值将禁用此行为。

> [ 'a', 'b', 'c' ]
[ 'a', 'b', 'c' ]
> _.length
3
> _ += 1
Expression assignment to _ now disabled.
4
> 1 + 1
2
> _
4 

类似地,_error将引用最后出现的错误(如果有)。显式地将_error设置为一个值将禁用此行为。

> throw new Error('foo');
Uncaught Error: foo
> _error.message
'foo' 
await关键字#

在顶层启用了对 await 关键字的支持。

> await Promise.resolve(123)
123
> await Promise.reject(new Error('REPL await'))
Uncaught Error: REPL await
    at REPL2:1:54
> const timeout = util.promisify(setTimeout);
undefined
> const old = Date.now(); await timeout(1000); console.log(Date.now() - old);
1002
undefined 

在 REPL 中使用 await 关键字的一个已知限制是,它将使 constlet 关键字的词法作用域失效。

例如

> const m = await Promise.resolve(123)
undefined
> m
123
> const m = await Promise.resolve(234)
undefined
> m
234 

--no-experimental-repl-await 将在 REPL 中禁用顶层 await。

反向 i 搜索#

REPL 支持类似于 ZSH 的双向反向 i 搜索。它由 Ctrl+R 触发以向后搜索,并由 Ctrl+S 触发以向前搜索。

重复的历史记录条目将被跳过。

一旦按下任何不对应于反向搜索的键,条目就会被接受。可以通过按下 EscCtrl+C 来取消。

更改方向会立即从当前位置开始搜索预期方向上的下一个条目。

自定义评估函数#

当创建一个新的 repl.REPLServer 时,可以提供一个自定义评估函数。例如,这可以用来实现完全自定义的 REPL 应用程序。

以下说明了一个假设的 REPL 示例,它执行从一种语言到另一种语言的文本翻译

const repl = require('node:repl');
const { Translator } = require('translator');

const myTranslator = new Translator('en', 'fr');

function myEval(cmd, context, filename, callback) {
  callback(null, myTranslator.translate(cmd));
}

repl.start({ prompt: '> ', eval: myEval }); 
可恢复错误#

在 REPL 提示符下,按下 Enter 会将当前输入行发送到 eval 函数。为了支持多行输入,eval 函数可以将 repl.Recoverable 的实例返回给提供的回调函数

function myEval(cmd, context, filename, callback) {
  let result;
  try {
    result = vm.runInThisContext(cmd);
  } catch (e) {
    if (isRecoverableError(e)) {
      return callback(new repl.Recoverable(e));
    }
  }
  callback(null, result);
}

function isRecoverableError(error) {
  if (error.name === 'SyntaxError') {
    return /^(Unexpected end of input|Unexpected token)/.test(error.message);
  }
  return false;
} 

自定义 REPL 输出#

默认情况下,repl.REPLServer 实例使用 util.inspect() 方法格式化输出,然后将输出写入提供的 Writable 流(默认情况下为 process.stdout)。showProxy 检查选项默认设置为 true,colors 选项根据 REPL 的 useColors 选项设置为 true。

可以在构造时指定 useColors 布尔选项,以指示默认写入器使用 ANSI 样式代码来为 util.inspect() 方法的输出着色。

如果 REPL 作为独立程序运行,也可以通过使用 inspect.replDefaults 属性从 REPL 内部更改 REPL 的 检查默认值,该属性反映了 util.inspect() 中的 defaultOptions

> util.inspect.replDefaults.compact = false;
false
> [1]
[
  1
]
> 

要完全自定义 repl.REPLServer 实例的输出,请在构造时为 writer 选项传入一个新函数。例如,以下示例只是将任何输入文本转换为大写

const repl = require('node:repl');

const r = repl.start({ prompt: '> ', eval: myEval, writer: myWriter });

function myEval(cmd, context, filename, callback) {
  callback(null, cmd);
}

function myWriter(output) {
  return output.toUpperCase();
} 

类:REPLServer#

repl.REPLServer 的实例是使用 repl.start() 方法或直接使用 JavaScript new 关键字创建的。

const repl = require('node:repl');

const options = { useColors: true };

const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options); 

事件:'exit'#

当 REPL 通过接收 .exit 命令作为输入、用户两次按下 Ctrl+C 来发出 SIGINT 信号,或通过按下 Ctrl+D 来发出输入流上的 'end' 信号而退出时,会发出 'exit' 事件。监听器回调函数将在没有任何参数的情况下被调用。

replServer.on('exit', () => {
  console.log('Received "exit" event from repl!');
  process.exit();
}); 

事件:'reset'#

当 REPL 的上下文被重置时,会发出 'reset' 事件。这发生在收到 .clear 命令作为输入时,除非 REPL 使用默认评估器,并且 repl.REPLServer 实例是在 useGlobal 选项设置为 true 的情况下创建的。监听器回调函数将使用对 context 对象的引用作为唯一参数被调用。

这主要用于将 REPL 上下文重新初始化为某个预定义状态

const repl = require('node:repl');

function initializeContext(context) {
  context.m = 'test';
}

const r = repl.start({ prompt: '> ' });
initializeContext(r.context);

r.on('reset', initializeContext); 

当执行此代码时,全局 'm' 变量可以被修改,但随后可以使用 .clear 命令将其重置为其初始值

$ ./node example.js
> m
'test'
> m = 1
1
> m
1
> .clear
Clearing context...
> m
'test'
> 

replServer.defineCommand(keyword, cmd)#

  • keyword <string> 命令关键字(包含前导 . 字符)。
  • cmd <Object> | <Function> 当命令被处理时要调用的函数。

replServer.defineCommand() 方法用于向 REPL 实例添加新的以 . 开头的命令。这些命令可以通过输入 . 后跟 keyword 来调用。cmd 可以是 Function 或具有以下属性的 Object

  • help <string> 当输入 .help 时要显示的帮助文本(可选)。
  • action <Function> 要执行的函数,可以选择接受单个字符串参数。

以下示例展示了向 REPL 实例添加的两个新命令

const repl = require('node:repl');

const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
  help: 'Say hello',
  action(name) {
    this.clearBufferedCommand();
    console.log(`Hello, ${name}!`);
    this.displayPrompt();
  },
});
replServer.defineCommand('saybye', function saybye() {
  console.log('Goodbye!');
  this.close();
}); 

然后可以在 REPL 实例中使用这些新命令

> .sayhello Node.js User
Hello, Node.js User!
> .saybye
Goodbye! 

replServer.displayPrompt([preserveCursor])#

replServer.displayPrompt() 方法使 REPL 实例准备好接收用户的输入,将配置的 prompt 打印到 output 中的新行,并恢复 input 以接受新的输入。

当输入多行内容时,会打印省略号而不是 'prompt'。

preserveCursortrue 时,光标位置不会重置为 0

replServer.displayPrompt 方法主要用于在使用 replServer.defineCommand() 方法注册的命令的 action 函数中调用。

replServer.clearBufferedCommand()#

replServer.clearBufferedCommand() 方法会清除所有已缓冲但尚未执行的命令。此方法主要用于在使用 replServer.defineCommand() 方法注册的命令的 action 函数中调用。

replServer.parseREPLKeyword(keyword[, rest])#

稳定性:0 - 已弃用。

  • keyword <string> 要解析和执行的潜在关键字
  • rest <any> 关键字命令的任何参数
  • 返回值:<boolean>

用于解析和执行 REPLServer 关键字的内部方法。如果 keyword 是有效的关键字,则返回 true,否则返回 false

replServer.setupHistory(historyPath, callback)#

为 REPL 实例初始化历史记录日志文件。在执行 Node.js 二进制文件并使用命令行 REPL 时,默认情况下会初始化历史记录文件。但是,在以编程方式创建 REPL 时,情况并非如此。在以编程方式使用 REPL 实例时,请使用此方法初始化历史记录日志文件。

repl.builtinModules#

所有 Node.js 模块名称的列表,例如 'http'

repl.start([options])#

  • options <Object> | <string>
    • prompt <string> 要显示的输入提示。默认值: '> '(带尾部空格)。
    • input <stream.Readable> 将从中读取 REPL 输入的 Readable 流。默认值: process.stdin
    • output <stream.Writable> 将向其写入 REPL 输出的 Writable 流。默认值: process.stdout
    • terminal <boolean> 如果为 true,则指定 output 应被视为 TTY 终端。默认值: 在实例化时检查 output 流的 isTTY 属性的值。
    • eval <Function> 用于评估每行给定输入的函数。默认值: JavaScript eval() 函数的异步包装器。eval 函数可以使用 repl.Recoverable 错误来指示输入不完整并提示输入更多行。
    • useColors <boolean> 如果为 true,则指定默认的 writer 函数应在 REPL 输出中包含 ANSI 颜色样式。如果提供了自定义 writer 函数,则此选项无效。默认值: 如果 REPL 实例的 terminal 值为 true,则检查 output 流上的颜色支持。
    • useGlobal <boolean> 如果为 true,则指定默认的评估函数将使用 JavaScript global 作为上下文,而不是为 REPL 实例创建一个新的独立上下文。Node CLI REPL 将此值设置为 true默认值: false
    • ignoreUndefined <boolean> 如果为 true,则指定默认写入器在命令返回值为 undefined 时不会输出返回值。默认值: false
    • writer <Function> 在写入 output 之前,用于格式化每个命令输出的函数。默认值: util.inspect().
    • completer <Function> 用于自定义 Tab 自动完成的可选函数。有关示例,请参阅 readline.InterfaceCompleter
    • replMode <symbol> 一个标志,指定默认评估器是否以严格模式或默认(松散)模式执行所有 JavaScript 命令。可接受的值为
      • repl.REPL_MODE_SLOPPY 以松散模式评估表达式。
      • repl.REPL_MODE_STRICT 以严格模式评估表达式。这等效于在每个 repl 语句之前加上 'use strict'
    • breakEvalOnSigint <boolean> 当收到 SIGINT 时停止评估当前代码段,例如按下 Ctrl+C 时。这不能与自定义 eval 函数一起使用。默认值: false
    • preview <boolean> 定义 repl 是否打印自动完成和输出预览。默认值: 使用默认 eval 函数时为 true,使用自定义 eval 函数时为 false。如果 terminal 为假值,则没有预览,preview 的值无效。
  • 返回值: <repl.REPLServer>

repl.start() 方法创建并启动一个 repl.REPLServer 实例。

如果 options 是一个字符串,则它指定输入提示符

const repl = require('node:repl');

// a Unix style prompt
repl.start('$ '); 

Node.js REPL#

Node.js 本身使用 node:repl 模块来提供自己的交互式界面,用于执行 JavaScript。这可以通过在不传递任何参数的情况下执行 Node.js 二进制文件(或传递 -i 参数)来实现。

$ node
> const a = [1, 2, 3];
undefined
> a
[ 1, 2, 3 ]
> a.forEach((v) => {
...   console.log(v);
...   });
1
2
3 

环境变量选项#

可以使用以下环境变量自定义 Node.js REPL 的各种行为

  • NODE_REPL_HISTORY: 当提供有效路径时,持久化 REPL 历史记录将保存到指定文件,而不是用户主目录中的 .node_repl_history。将此值设置为 ''(空字符串)将禁用持久化 REPL 历史记录。空白字符将从值中修剪。在 Windows 平台上,具有空值的環境變數無效,因此將此變數設置為一個或多個空格以禁用持久化 REPL 历史记录。
  • NODE_REPL_HISTORY_SIZE: 控制如果历史记录可用,将持久化的历史记录行数。必须是正数。默认值:1000
  • NODE_REPL_MODE: 可以是 'sloppy''strict'默认值:'sloppy',这将允许运行非严格模式代码。

持久化历史记录#

默认情况下,Node.js REPL 会通过将输入保存到用户主目录中的 .node_repl_history 文件来持久化 node REPL 会话之间的历史记录。可以通过设置环境变量 NODE_REPL_HISTORY='' 来禁用此功能。

使用 Node.js REPL 与高级行编辑器#

对于高级行编辑器,使用环境变量 NODE_NO_READLINE=1 启动 Node.js。这将在规范终端设置中启动主 REPL 和调试器 REPL,这将允许使用 rlwrap

例如,以下内容可以添加到 .bashrc 文件中

alias node="env NODE_NO_READLINE=1 rlwrap node" 

启动针对单个运行实例的多个 REPL 实例#

可以创建和运行针对单个运行的 Node.js 实例的多个 REPL 实例,这些实例共享单个 global 对象,但具有单独的 I/O 接口。

例如,以下示例在stdin、Unix 套接字和 TCP 套接字上提供了独立的 REPL。

const net = require('node:net');
const repl = require('node:repl');
let connections = 0;

repl.start({
  prompt: 'Node.js via stdin> ',
  input: process.stdin,
  output: process.stdout,
});

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via Unix socket> ',
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen('/tmp/node-repl-sock');

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via TCP socket> ',
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen(5001); 

从命令行运行此应用程序将启动一个在 stdin 上的 REPL。其他 REPL 客户端可以通过 Unix 套接字或 TCP 套接字连接。例如,telnet 可用于连接到 TCP 套接字,而socat 可用于连接到 Unix 和 TCP 套接字。

通过从基于 Unix 套接字的服务器而不是 stdin 启动 REPL,可以连接到长时间运行的 Node.js 进程,而无需重新启动它。

有关在net.Servernet.Socket 实例上运行“功能齐全”(terminal)REPL 的示例,请参阅:https://gist.github.com/TooTallNate/2209310

有关在 curl(1) 上运行 REPL 实例的示例,请参阅:https://gist.github.com/TooTallNate/2053342