REPL
History
稳定性:2 - 稳定
node:repl 模块提供一个读取 - 求值 - 输出 循环 (REPL) 实现,
既可用作独立程序,也可包含在其他应用程序中。可以通过以下方式访问:
import repl from 'node:repl';node:repl 模块导出 repl.REPLServer 类。运行时,
repl.REPLServer 的实例将接受单行用户输入,
根据用户定义的求值函数对其进行求值,然后输出结果。输入和输出可能分别来自 stdin 和 stdout,或者
可以连接到任何 Node.js [流][]。
repl.REPLServer 的实例支持输入自动补全、
补全预览、简单的 Emacs 风格行编辑、多行输入、
类 ZSH 反向 i 搜索、类 ZSH 基于子串的历史搜索、
ANSI 风格输出、保存和恢复当前 REPL 会话状态、错误
恢复以及可自定义的求值函数。不支持
ANSI 风格和 Emacs 风格行编辑的终端会自动回退到有限的功能集。
所有 REPL 实例都支持以下特殊命令:
.break:在输入多行表达式的过程中,输入.break命令(或按 Ctrl+C)以中止 该表达式的进一步输入或处理。.clear:将 REPLcontext重置为空对象并清除任何 正在输入的多行表达式。.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
// 进入编辑器模式 (^D 完成,^C 取消)
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 表达式:
> 1 + 1
2
> const m = 2
undefined
> m + 1
3除非在块或函数内另有作用域,否则使用 const、let 或 var 关键字
隐式或使用这些关键字声明的变量
都在全局作用域声明。
默认求值器提供对全局作用域中存在的任何变量的访问。可以通过将变量赋值给与每个 REPLServer 关联的 context 对象,从而显式地将变量暴露给 REPL:
import repl from 'node:repl';
const msg = 'message';
repl.start('> ').context.m = msg;context 对象中的属性在 REPL 内显示为局部变量:
$ node repl_test.js
> m
'message'上下文属性默认不是只读的。要指定只读全局变量,
必须使用 Object.defineProperty() 定义上下文属性:
import repl from 'node:repl';
const msg = 'message';
const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
configurable: false,
enumerable: true,
value: msg,
});默认求值器在使用时会自动将 Node.js 核心模块加载到
REPL 环境中。例如,除非另外声明为
全局或作用域变量,否则输入 fs 将按需求值为
global.fs = require('node:fs')。
> fs.createReadStream('./some/file');REPL 使用 domain 模块来捕获该
REPL 会话的所有未捕获异常。
在 REPL 中使用 domain 模块有以下副作用:
-
未捕获异常仅在独立 REPL 中发出
'uncaughtException'事件。在另一个 Node.js 程序中的 REPL 内为此事件添加监听器会导致ERR_INVALID_REPL_INPUT。const r = repl.start(); r.write('process.on("uncaughtException", () => console.log("Foobar"));\n'); // 输出流包括: // TypeError [ERR_INVALID_REPL_INPUT]: uncaughtException 的监听器 // 不能在 REPL 中使用 r.close(); -
尝试使用
process.setUncaughtExceptionCaptureCallback()会抛出ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE错误。
默认情况下,默认求值器会将最近求值的表达式结果赋值给特殊变量 _ (下划线)。
显式设置 _ 为某个值将禁用此行为。
> [ '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 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 关键字的一个已知限制是
它会使 const 关键字的词法作用域失效。
例如:
> const m = await Promise.resolve(123)
undefined
> m
123
> m = await Promise.resolve(234)
234
// 重新声明常量会报错
> const m = await Promise.resolve(345)
Uncaught SyntaxError: Identifier 'm' has already been declared--no-experimental-repl-await 将禁用 REPL 中的顶层 await。
反向 i 搜索
History
REPL 支持类似于 ZSH 的双向反向 i 搜索。它由 Ctrl+R 触发向后搜索 和 Ctrl+S 触发向前搜索。
重复的历史条目将被跳过。
一旦按下任何不对应 反向搜索的键,条目即被接受。可以通过按 Esc 或 Ctrl+C 来取消。
改变方向会立即从当前位置开始向预期 方向搜索下一个条目。
当创建新的 repl.REPLServer 时,可以提供自定义求值函数。例如,这可用于实现完全自定义的 REPL
应用程序。
求值函数接受以下四个参数:
<string>1 + 1
)。<Object>global
上下文,也可以是特定于 REPL 实例的上下文,具体取决于
useGlobal
选项。<string><Function>以下示例说明了一个对给定数字求平方的 REPL,如果 提供的输入实际上不是数字,则打印错误:
import repl from 'node:repl';
function byThePowerOfTwo(number) {
return number * number;
}
function myEval(code, context, replResourceName, callback) {
if (isNaN(code)) {
callback(new Error(`${code.trim()} is not a number`));
} else {
callback(null, byThePowerOfTwo(code));
}
}
repl.start({ prompt: 'Enter a number: ', 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.REPLServer 实例在将输出写入提供的 Writable
流(默认为 process.stdout)之前,使用
util.inspect() 方法格式化输出。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 选项传入新
函数。例如,以下示例简单地将任何输入文本转换为大写:
import repl from '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
History
repl.start()repl.REPLServer 的实例是使用 repl.start() 方法创建的,或者直接使用 JavaScript new 关键字创建。
import repl from 'node:repl';
const options = { useColors: true };
const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options);事件:'exit'
History
当通过接收 .exit 命令作为输入、用户按两次 Ctrl+C 发出 SIGINT 信号,或按 Ctrl+D 在输入流上发出 'end' 信号而退出 REPL 时,会发出 'exit' 事件。监听器回调被调用时不带任何参数。
replServer.on('exit', () => {
console.log('Received "exit" event from repl!');
process.exit();
});事件:'reset'
History
当 REPL 的上下文被重置时,会发出 'reset' 事件。每当收到 .clear 命令作为输入时就会发生这种情况,除非 REPL 正在使用默认评估器且 repl.REPLServer 实例创建时将 useGlobal 选项设置为 true。监听器回调将被调用,并仅传入对 context 对象的引用作为唯一参数。
这主要用于将 REPL 上下文重新初始化为某些预定义状态:
import repl from '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): void<string>.
字符)。<Object>
|
<Function>replServer.defineCommand() 方法用于向 REPL 实例添加新的 . 前缀命令。此类命令通过输入 . 后跟 keyword 来调用。cmd 是一个 Function 或具有以下属性的 Object:
<string>.help
时显示的帮助文本(可选)。<Function>以下示例显示了添加到 REPL 实例的两个新命令:
import repl from '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?): void<boolean>replServer.displayPrompt() 方法使 REPL 实例准备好接收用户输入,将配置的 prompt 打印到 output 中的新行,并恢复 input 以接受新输入。
当输入多行内容时,会打印管道符 '|' 而不是提示符。
当 preserveCursor 为 true 时,光标位置不会重置为 0。
replServer.displayPrompt 方法主要旨在从使用 replServer.defineCommand() 方法注册的命令的动作函数内部调用。
replServer.clearBufferedCommand(): voidreplServer.clearBufferedCommand() 方法清除任何已缓冲但尚未执行的命令。此方法主要旨在从使用 replServer.defineCommand() 方法注册的命令的动作函数内部调用。
replServer.setupHistory(historyConfig, callback): void<Function>historyConfig
中作为
onHistoryFileLoaded
提供,则为可选)<Error><repl.REPLServer>为 REPL 实例初始化历史日志文件。当执行 Node.js 二进制文件并使用命令行 REPL 时,默认情况下会初始化历史文件。但是,以编程方式创建 REPL 时并非如此。当以编程方式使用 REPL 实例时,使用此方法初始化历史日志文件。
repl.builtinModules
History
稳定性:0 - 已弃用。请改用
module.builtinModules。
- 类型:
<string[]>
一些 Node.js 模块名称的列表,例如 'http'。
提供了一个自动迁移工具(源代码):
npx codemod@latest @nodejs/repl-builtin-modulesrepl.start
History
The handleError parameter has been added.
Added the possibility to add/edit/remove multilineswhile adding a multiline command.
The multi-line indicator is now '|' instead of '...'. Added support for multi-line history. It is now possible to 'fix' multi-line commands with syntax errors by visiting the history and editing the command. When visiting the multiline history from an old node version, the multiline structure is not preserved.
The preview option is now available.
The terminal option now follows the default description inall cases and useColors checks hasColors() if available.
The REPL_MAGIC_MODE replMode was removed.
The breakEvalOnSigint option is supported now.
The options parameter is optional now.
repl.start(options?): void<string>'> '
(带尾随空格)。<stream.Readable>Readable
流。
默认值:
process.stdin
。<stream.Writable>Writable
流。
默认值:
process.stdout
。<boolean>true
,指定
output
应被视为 TTY 终端。
默认值:
实例化时检查
output
流上的
isTTY
属性值。<Function>eval()
函数的异步包装器。
eval
函数可以使用
repl.Recoverable
报错,以指示输入不完整并提示输入额外的行。有关更多详细信息,请参阅
自定义评估函数
部分。<boolean>true
,指定默认
writer
函数应在 REPL 输出中包含 ANSI 颜色样式。如果提供了自定义
writer
函数,则此选项无效。
默认值:
如果 REPL 实例的
terminal
值为
true
,则检查
output
流上的颜色支持。<boolean>true
,指定默认评估函数将使用 JavaScript
global
作为上下文,而不是为 REPL 实例创建新的单独上下文。node CLI REPL 将此值设置为
true
。
默认值:
false
。<boolean>true
,指定默认写入器如果命令的返回值为
undefined
则不会输出该返回值。
默认值:
false
。<Function>output
之前调用该函数以格式化每个命令的输出。
默认值:
util.inspect()
。<Function>readline.InterfaceCompleter
获取示例。<symbol>'use strict'
。<boolean>SIGINT
时停止评估当前代码片段,例如当按下
Ctrl
+
C
时。这不能与自定义
eval
函数一起使用。
默认值:
false
。<boolean>true
,使用自定义 eval 函数时为
false
。如果
terminal
为假值,则没有预览,
preview
的值无效。<Function>'uncaughtException'
事件。
'unhandled'
值在
REPLServer
实例已关闭的情况下是否可取,取决于特定的用例。repl.start() 方法创建并启动 repl.REPLServer 实例。
如果 options 是字符串,则它指定输入提示符:
import repl from 'node:repl';
// 一个 Unix 风格的提示符
repl.start('$ ');Node.js 本身使用 node:repl 模块来提供其自身的交互式界面以执行 JavaScript。可以通过在不传递任何参数(或传递 -i 参数)的情况下执行 Node.js 二进制文件来使用它:
$ 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_NO_READLINE=1 启动 Node.js。这将以规范终端设置启动主 REPL 和调试器 REPL,这将允许与 rlwrap 一起使用。
例如,可以将以下内容添加到 .bashrc 文件中:
alias node="env NODE_NO_READLINE=1 rlwrap node"可以针对单个正在运行的 Node.js 实例创建并运行多个 REPL 实例,它们共享单个 global 对象(通过将 useGlobal 选项设置为 true),但具有单独的 I/O 接口。
例如,以下代码在 stdin、Unix socket 和 TCP socket 上提供单独的 REPL,所有这些都共享相同的 global 对象:
import net from 'node:net';
import repl from 'node:repl';
import process from 'node:process';
import fs from 'node:fs';
let connections = 0;
repl.start({
prompt: 'Node.js via stdin> ',
useGlobal: true,
input: process.stdin,
output: process.stdout,
});
const unixSocketPath = '/tmp/node-repl-sock';
// 如果 socket 文件已存在,让我们移除它
fs.rmSync(unixSocketPath, { force: true });
net.createServer((socket) => {
connections += 1;
repl.start({
prompt: 'Node.js via Unix socket> ',
useGlobal: true,
input: socket,
output: socket,
}).on('exit', () => {
socket.end();
});
}).listen(unixSocketPath);
net.createServer((socket) => {
connections += 1;
repl.start({
prompt: 'Node.js via TCP socket> ',
useGlobal: true,
input: socket,
output: socket,
}).on('exit', () => {
socket.end();
});
}).listen(5001);从命令行运行此应用程序将在 stdin 上启动 REPL。其他 REPL 客户端可以通过 Unix socket 或 TCP socket 连接。例如,telnet 对于连接 TCP sockets 很有用,而 socat 可用于连接 Unix 和 TCP sockets。
通过从基于 Unix socket 的服务器启动 REPL 而不是从 stdin 启动,可以在不重启的情况下连接到长期运行的 Node.js 进程。
这是一个如何使用 net.Server 和 net.Socket 运行“功能齐全”(终端)REPL 的示例。
以下脚本启动一个端口为 1337 的 HTTP 服务器,允许客户端建立与其 REPL 实例的 socket 连接。
// repl-server.js
import repl from 'node:repl';
import net from 'node:net';
net
.createServer((socket) => {
const r = repl.start({
prompt: `socket ${socket.remoteAddress}:${socket.remotePort}> `,
input: socket,
output: socket,
terminal: true,
useGlobal: false,
});
r.on('exit', () => {
socket.end();
});
r.context.socket = socket;
})
.listen(1337);而以下代码实现了一个客户端,可以通过端口 1337 与上述定义的服务器创建 socket 连接。
// repl-client.js
import net from 'node:net';
import process from 'node:process';
const sock = net.connect(1337);
process.stdin.pipe(sock);
sock.pipe(process.stdout);
sock.on('connect', () => {
process.stdin.resume();
process.stdin.setRawMode(true);
});
sock.on('close', () => {
process.stdin.setRawMode(false);
process.stdin.pause();
sock.removeListener('close', done);
});
process.stdin.on('end', () => {
sock.destroy();
console.log();
});
process.stdin.on('data', (b) => {
if (b.length === 1 && b[0] === 4) {
process.stdin.emit('end');
}
});要运行此示例,请在机器上打开两个不同的终端,在一个终端中使用 node repl-server.js 启动服务器,在另一个终端中使用 node repl-client.js。
原始代码来自 https://gist.github.com/TooTallNate/2209310。
这是一个如何通过 curl() 运行 REPL 实例的示例。
以下脚本启动一个端口为 8000 的 HTTP 服务器,可以接受通过 curl() 建立的连接。
import http from 'node:http';
import repl from 'node:repl';
const server = http.createServer((req, res) => {
res.setHeader('content-type', 'multipart/octet-stream');
repl.start({
prompt: 'curl repl> ',
input: req,
output: res,
terminal: false,
useColors: true,
useGlobal: false,
});
});
server.listen(8000);当上述脚本运行时,您可以使用 curl() 连接到服务器,并通过运行 curl --no-progress-meter -sSNT. localhost:8000 连接到其 REPL 实例。
警告 此示例仅用于教育目的,以演示如何使用不同的 I/O 流启动 Node.js REPL。 不应 在生产环境或任何涉及安全问题的上下文中使用,除非采取额外的保护措施。 如果需要在现实世界的应用程序中实现 REPL,请考虑替代方法来减轻这些风险,例如使用安全的输入机制并避免开放的网络接口。