Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,16 @@ added:
Enable experimental support for the QUIC protocol.

### `--experimental-repl-typescript`

<!--
added: REPLACEME
-->

> Stability: 1.2 - Release candidate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why release candidate

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underlying transformer, https://nodejs.org/api/module.html#modulestriptypescripttypescode-options, is a release candidate, I figured this would be an extension of that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has a larger scope than that api

Enable experimental support for TypeScript stripping in the REPL.

### `--experimental-sea-config`

<!-- YAML
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/main/check_syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function loadESMIfNeeded(cb) {

async function checkSyntax(source, filename) {
let format;
if (filename === '[stdin]' || filename === '[eval]') {
if (filename === '[stdin]' || filename === '[eval]' || filename === '[repl]') {
format = (getOptionValue('--input-type') === 'module') ? 'module' : 'commonjs';
} else {
const { defaultResolve } = require('internal/modules/esm/resolve');
Expand All @@ -75,3 +75,7 @@ async function checkSyntax(source, filename) {

wrapSafe(filename, source, undefined, format);
}

module.exports = {
checkSyntax,
};
1 change: 1 addition & 0 deletions lib/internal/modules/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,5 @@ module.exports = {
stripTypeScriptModuleTypes,
stripTypeScriptTypes,
stripTypeScriptTypesForCoverage,
processTypeScriptCode,
};
68 changes: 46 additions & 22 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const {
decorateErrorStack,
isError,
deprecate,
getLazy,
SideEffectFreeRegExpPrototypeSymbolReplace,
SideEffectFreeRegExpPrototypeSymbolSplit,
} = require('internal/util');
Expand Down Expand Up @@ -141,6 +142,9 @@ const {
const experimentalREPLAwait = getOptionValue(
'--experimental-repl-await',
);
const experimentalREPLTypeScript = getOptionValue(
'--experimental-repl-typescript',
);
const pendingDeprecation = getOptionValue('--pending-deprecation');
const {
REPL_MODE_SLOPPY,
Expand Down Expand Up @@ -177,8 +181,12 @@ const {

// Lazy-loaded.
let processTopLevelAwait;
const processTypeScriptCode = getLazy(() => require('internal/modules/typescript').processTypeScriptCode);
const checkSyntax = getLazy(() => require('internal/main/check_syntax').checkSyntax);

const parentModule = module;
const kREPLTag = '[repl]';
const kTypeScriptOptions = { __proto__: null, mode: 'strip-only', filename: kREPLTag };

// AsyncLocalStorage to track which REPL instance owns the current async context
// This replaces the domain-based tracking for error handling
Expand Down Expand Up @@ -477,6 +485,42 @@ class REPLServer extends Interface {
phase === 'evaluation' ? cascadedLoader.kEvaluationPhase :
cascadedLoader.kSourcePhase);
}

function runScript(scriptCode) {
if (experimentalREPLTypeScript) {
try {
// Try parsing as pure JS
checkSyntax()(scriptCode, kREPLTag);
} catch (originalError) {
try {
// That failed, so try stripping TypeScript types
scriptCode = processTypeScriptCode()(scriptCode, kTypeScriptOptions);
} catch (tsError) {
// If it's invalid or unsupported TypeScript syntax, rethrow the original error
// with the TypeScript error message added to the stack.
if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' ||
tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
throw originalError;
}

throw tsError;
}
}
}

return makeContextifyScript(
scriptCode, // code
file, // filename,
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);
}
// `experimentalREPLAwait` is set to true by default.
// Shall be false in case `--no-experimental-repl-await` flag is used.
if (experimentalREPLAwait && StringPrototypeIncludes(code, 'await')) {
Expand All @@ -498,17 +542,7 @@ class REPLServer extends Interface {
// in order to detect if error is truly non recoverable
const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(/\bawait\b/g, code, '');
try {
makeContextifyScript(
fallbackCode, // code
file, // filename,
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);
runScript(fallbackCode);
} catch (fallbackError) {
if (isRecoverableError(fallbackError, fallbackCode)) {
recoverableError = true;
Expand Down Expand Up @@ -536,17 +570,7 @@ class REPLServer extends Interface {
// value for statements and declarations that don't return a value.
code = `'use strict'; void 0;\n${code}`;
}
script = makeContextifyScript(
code, // code
file, // filename,
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);
script = runScript(code);
} catch (e) {
debug('parse error %j', code, e);
if (wrappedCmd) {
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::experimental_repl_await,
kAllowedInEnvvar,
true);
AddOption("--experimental-repl-typescript",
"experimental TypeScript support in REPL",
&EnvironmentOptions::experimental_repl_typescript,
kAllowedInEnvvar);
AddOption("--experimental-vm-modules",
"experimental ES Module support in vm module",
&EnvironmentOptions::experimental_vm_modules,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class EnvironmentOptions : public Options {
bool allow_ffi = false;
bool allow_worker_threads = false;
bool experimental_repl_await = true;
bool experimental_repl_typescript = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool experimental_repl_typescript = false;
bool experimental_repl_typescript = EXPERIMENTALS_DEFAULT_VALUE;

bool experimental_vm_modules = EXPERIMENTALS_DEFAULT_VALUE;
bool async_context_frame = true;
bool expose_internals = false;
Expand Down
15 changes: 15 additions & 0 deletions test/parallel/test-repl-typescript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Flags: --experimental-repl-typescript
'use strict';
require('../common');
const assert = require('assert');

const { startNewREPLServer } = require('../common/repl');

const { input, output } = startNewREPLServer({ terminal: false, prompt: '> ' });

input.emit('data', 'let x: number = 3\n');
assert.match(output.accumulator, /undefined\n> /);

Check failure on line 11 in test/parallel/test-repl-typescript.js

View workflow job for this annotation

GitHub Actions / aarch64-darwin: with shared libraries

--- stderr --- node:internal/assert/utils:146 throw error; ^ AssertionError [ERR_ASSERTION]: The input did not match the regular expression /undefined\n> /. Input: '> Uncaught Error [ERR_NO_TYPESCRIPT]: Node.js is not compiled with TypeScript support\n' + ' at assertTypeScript (node:internal/util:247:11)\n' + ' at node:internal/modules/typescript:35:3 {\n' + " code: 'ERR_NO_TYPESCRIPT'\n" + '}\n' + '> ' at Object.<anonymous> (/Users/runner/work/_temp/node-v27.0.0-nightly2026-06-23c03c9f82e8-slim/test/parallel/test-repl-typescript.js:11:8) at Module._compile (node:internal/modules/cjs/loader:1947:14) at Object..js (node:internal/modules/cjs/loader:2087:10) at Module.load (node:internal/modules/cjs/loader:1669:32) at Module._load (node:internal/modules/cjs/loader:1450:12) at wrapModuleLoad (node:internal/modules/cjs/loader:260:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { generatedMessage: true, code: 'ERR_ASSERTION', actual: '> Uncaught Error [ERR_NO_TYPESCRIPT]: Node.js is not compiled with TypeScript support\n' + ' at assertTypeScript (node:internal/util:247:11)\n' + ' at node:internal/modules/typescript:35:3 {\n' + " code: 'ERR_NO_TYPESCRIPT'\n" + '}\n' + '> ', expected: /undefined\n> /, operator: 'match', diff: 'simple' } Node.js v27.0.0-pre Command: out/Release/node --experimental-repl-typescript /Users/runner/work/_temp/node-v27.0.0-nightly2026-06-23c03c9f82e8-slim/test/parallel/test-repl-typescript.js

Check failure on line 11 in test/parallel/test-repl-typescript.js

View workflow job for this annotation

GitHub Actions / x86_64-darwin: with shared libraries

--- stderr --- node:internal/assert/utils:146 throw error; ^ AssertionError [ERR_ASSERTION]: The input did not match the regular expression /undefined\n> /. Input: '> Uncaught Error [ERR_NO_TYPESCRIPT]: Node.js is not compiled with TypeScript support\n' + ' at assertTypeScript (node:internal/util:247:11)\n' + ' at node:internal/modules/typescript:35:3 {\n' + " code: 'ERR_NO_TYPESCRIPT'\n" + '}\n' + '> ' at Object.<anonymous> (/Users/runner/work/_temp/node-v27.0.0-nightly2026-06-23c03c9f82e8-slim/test/parallel/test-repl-typescript.js:11:8) at Module._compile (node:internal/modules/cjs/loader:1947:14) at Object..js (node:internal/modules/cjs/loader:2087:10) at Module.load (node:internal/modules/cjs/loader:1669:32) at Module._load (node:internal/modules/cjs/loader:1450:12) at wrapModuleLoad (node:internal/modules/cjs/loader:260:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { generatedMessage: true, code: 'ERR_ASSERTION', actual: '> Uncaught Error [ERR_NO_TYPESCRIPT]: Node.js is not compiled with TypeScript support\n' + ' at assertTypeScript (node:internal/util:247:11)\n' + ' at node:internal/modules/typescript:35:3 {\n' + " code: 'ERR_NO_TYPESCRIPT'\n" + '}\n' + '> ', expected: /undefined\n> /, operator: 'match', diff: 'simple' } Node.js v27.0.0-pre Command: out/Release/node --experimental-repl-typescript /Users/runner/work/_temp/node-v27.0.0-nightly2026-06-23c03c9f82e8-slim/test/parallel/test-repl-typescript.js
output.accumulator = '';

input.emit('data', 'x\n');
assert.match(output.accumulator, /3\n> /);
Loading