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
History
更新了 historyConfig 参数,使其接受包含 filePath、size、removeHistoryDuplicates 和 onHistoryFileLoaded 属性的对象。
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
已添加 handleError 参数。
在添加多行命令时,增加了添加/编辑/删除多行的可能性。
多行指示符现在是 |,而不是 ...。增加了对多行历史记录的支持。现在可以通过访问历史记录并编辑命令来“修复”带有语法错误的多行命令。当从旧版 node 访问多行历史记录时,多行结构不会被保留。
现在可以使用 preview 选项。
在所有情况下,terminal 选项现在都遵循默认描述,且 useColors 会在可用时检查 hasColors()。
移除了 REPL_MAGIC_MODE replMode。
现在支持 breakEvalOnSigint 选项。
现在 options 参数是可选的。
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,请考虑替代方法来减轻这些风险,例如使用安全的输入机制并避免开放的网络接口。