REPL#

穩定性:2 - 穩定

node:repl 模組提供了一種 Read-Eval-Print-Loop (REPL) 實現,既可以作為獨立程式使用,也可以包含在其他應用程式中。可以透過以下方式訪問:

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

設計與特性#

node:repl 模組匯出了 repl.REPLServer 類。在執行時,repl.REPLServer 的例項會接收使用者輸入的每一行,根據使用者定義的求值函式進行求值,然後輸出結果。輸入和輸出可以分別來自 stdinstdout,也可以連線到任何 Node.js

repl.REPLServer 的例項支援自動補全輸入、補全預覽、簡易的 Emacs 風格行編輯、多行輸入、類似 ZSH 的反向搜尋 (reverse-i-search)、類似 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:在空白行上按下時,顯示全域性和區域性(作用域)變數。在輸入其他內容時按下時,顯示相關的自動補全選項。

有關反向搜尋相關的鍵繫結,請參見 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。

import repl from 'node:repl';
const msg = 'message';

repl.start('> ').context.m = msg;
const repl = require('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,
});
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 關鍵字的一個已知限制是,它會使 const 關鍵字的詞法作用域失效。

例如:

> const m = await Promise.resolve(123)
undefined
> m
123
> m = await Promise.resolve(234)
234
// redeclaring the constant does error
> const m = await Promise.resolve(345)
Uncaught SyntaxError: Identifier 'm' has already been declared

--no-experimental-repl-await 將停用 REPL 中的頂層 await。

反向搜尋 (Reverse-i-search)#

REPL 支援類似於 ZSH 的雙向反向搜尋。透過 Ctrl+R 向後搜尋,透過 Ctrl+S 向前搜尋。

重複的歷史記錄條目將被跳過。

一旦按下任何不對應於反向搜尋的按鍵,條目即被接受。可以透過按下 EscCtrl+C 取消。

改變方向會立即從當前位置開始沿預期方向搜尋下一個條目。

自定義求值函式#

建立新的 repl.REPLServer 時,可以提供自定義求值函式。這可用於實現完全自定義的 REPL 應用程式。

求值函式接收以下四個引數:

  • code <string> 要執行的程式碼(例如 1 + 1)。
  • context <Object> 執行程式碼的上下文。根據 useGlobal 選項,這可以是 JavaScript 的 global 上下文,也可以是特定於 REPL 例項的上下文。
  • replResourceName <string> 與當前程式碼求值關聯的 REPL 資源識別符號。這對於除錯非常有用。
  • callback <Function> 程式碼求值完成後呼叫的函式。該回調函式接收兩個引數:
    • 如果求值期間發生錯誤,則提供一個錯誤物件;如果未發生錯誤,則為 null/undefined
    • 程式碼求值的結果(如果提供了錯誤,則此引數無關緊要)。

以下示例演示了一個將給定數字平方的 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 });
const repl = require('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 輸出#

預設情況下,repl.REPLServer 例項在將輸出寫入提供的 Writable 流(預設為 process.stdout)之前,使用 util.inspect() 方法格式化輸出。showProxy 檢查選項預設設定為 true,且 colors 選項根據 REPL 的 useColors 選項設定為 true

可以在構造時指定 useColors 布林選項,以指示預設寫入器使用 ANSI 樣式程式碼為 util.inspect() 方法的輸出著色。如果提供了自定義 writer 函式,則此選項無效。

如果 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();
}
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 關鍵字建立。

import repl from 'node:repl';

const options = { useColors: true };

const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options);
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 上下文重新初始化為某種預定義狀態。

import repl from 'node:repl';

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

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

r.on('reset', initializeContext);
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)#

replServer.defineCommand() 方法用於向 REPL 例項新增新的以 . 開頭的命令。此類命令透過輸入 . 後跟 keyword 來呼叫。cmd 可以是 Function,或者是具有以下屬性的 Object

  • help <string> 輸入 .help 時顯示的幫助文字(可選)。
  • action <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();
});
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 例項做好接收使用者輸入的準備,在 output 中列印已配置的 prompt 到新行,並恢復 input 以接受新的輸入。

當輸入多行時,會列印一個管道符 '|',而不是提示符。

preserveCursortrue 時,游標位置不會重置為 0

replServer.displayPrompt 方法主要旨在從使用 replServer.defineCommand() 方法註冊的命令的 action 函式中呼叫。

replServer.clearBufferedCommand()#

replServer.clearBufferedCommand() 方法清除任何已緩衝但尚未執行的命令。此方法主要旨在從使用 replServer.defineCommand() 方法註冊的命令的 action 函式中呼叫。

replServer.setupHistory(historyConfig, callback)#

  • historyConfig <Object> | <string> 歷史記錄檔案的路徑。如果是字串,則為歷史記錄檔案的路徑。如果是物件,則可以具有以下屬性:
    • filePath <string> 歷史記錄檔案的路徑。
    • size <number> 保留的歷史記錄行數上限。要停用歷史記錄,請將此值設定為 0。此選項僅在使用者將 terminal 設定為 true 或由內部 output 檢查確定為 true 時才有意義,否則不會初始化歷史記錄快取機制。預設值: 30
    • removeHistoryDuplicates <boolean> 如果為 true,則當新增到歷史記錄列表的新輸入行與舊行重複時,將從列表中刪除舊行。預設值: false
    • onHistoryFileLoaded <Function> 當歷史記錄準備好寫入或出錯時呼叫。
  • callback <Function> 當歷史記錄準備好寫入或出錯時呼叫(如果在 historyConfig 中提供了 onHistoryFileLoaded,則為可選)。

為 REPL 例項初始化歷史記錄日誌檔案。當執行 Node.js 二進位制檔案並使用命令列 REPL 時,預設會初始化一個歷史記錄檔案。然而,以程式設計方式建立 REPL 時並非如此。在使用 REPL 例項進行程式設計時,請使用此方法初始化歷史記錄日誌檔案。

repl.builtinModules#

穩定性:0 - 已棄用。請改為使用 module.builtinModules

一些 Node.js 模組的名稱列表,例如 'http'

提供有自動遷移工具(原始碼

npx codemod@latest @nodejs/repl-builtin-modules

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 的值不起作用。
    • handleError <Function> 此函式自定義 REPL 中的錯誤處理。它接收丟擲的異常作為其第一個引數,並且必須同步返回以下值之一:
      • 'print' 將錯誤列印到輸出流(預設行為)。
      • 'ignore' 跳過所有剩餘的錯誤處理。
      • 'unhandled' 將異常視為完全未處理。在這種情況下,錯誤將傳遞給程序範圍的異常處理程式,例如 'uncaughtException' 事件。取決於具體用例,在 REPLServer 例項已關閉的情況下,'unhandled' 值可能是可取的,也可能不是。
  • 返回:<repl.REPLServer>

repl.start() 方法建立並啟動一個 repl.REPLServer 例項。

如果 options 是一個字串,則它指定輸入提示符。

import repl from 'node:repl';

// a Unix style prompt
repl.start('$ ');
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 物件(透過將 useGlobal 選項設定為 true),但具有獨立的 I/O 介面。

例如,以下示例在 stdin、Unix 套接字和 TCP 套接字上提供了獨立的 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';

// If the socket file already exists let's remove it
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);
const net = require('node:net');
const repl = require('node:repl');
const fs = require('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';

// If the socket file already exists let's remove it
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 套接字或 TCP 套接字連線。例如,telnet 對於連線 TCP 套接字非常有用,而 socat 可用於連線 Unix 和 TCP 套接字。

透過從基於 Unix 套接字的伺服器而不是 stdin 啟動 REPL,可以在不重啟的情況下連線到長時間執行的 Node.js 程序。

示例#

透過 net.Servernet.Socket 實現全功能“終端” REPL#

這是一個關於如何使用 net.Servernet.Socket 執行“全功能”(終端)REPL 的示例。

以下指令碼在埠 1337 上啟動 HTTP 伺服器,允許客戶端建立套接字連線到其 REPL 例項。

// 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);
// repl-server.js
const repl = require('node:repl');
const net = require('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 建立套接字連線的客戶端。

// 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');
  }
});
// repl-client.js
const net = require('node:net');

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#

這是一個關於如何透過 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);
const http = require('node:http');
const repl = require('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,請考慮使用其他能夠降低風險的方法,例如使用安全的輸入機制並避免公開網路介面。

原始程式碼來自 https://gist.github.com/TooTallNate/2053342