You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Great work on ts-node! I just looked through the code a bit, and you all have clearly put a lot of thought and time into it. Thank you.
One request: I love using the REPL for quick exploration/experimentation/learning. And that often involves async steps, e.g. DB queries or API requests.
Before TypeScript, we used to use Streamline.js, and I loved that its REPL supported making async calls and awaiting their responses:
I understand that TypeScript and ES6/7 as languages don't allow top-level await in modules, but would you be open to supporting that in the REPL?
The way the Streamline REPL does it is simply by wrapping the code in an async IIFE, and only calling the REPL eval callback once the async IIFE finishes:
Off then top of my head, I don't think it's possible to support without more hacking of the TypeScript compiler and/or into the future with ES6 modules. You need an async function for await to compile, but you can't import from inside a function (it must be at the top-level). Happy to keep the issue open though.
There is top level await in the node 10+ repl and it could theoretically be utilized running node --experimental-repl-await --eval 'require("./node_modules/ts-node/dist/bin.js")'. However, this is stopped by the TypeScript compiler complaining
> const a: string = await Promise.resolve("a")
[eval].ts(1,19): error TS1308: 'await' expression is only allowed within an async function.
which is totally right, since top level await is not (yet?) valid JavaScript but a hack for repl convenience.
I don't see a way to get the await keyword untouched through the TypeScript compiler.
@webmaster128 If I understand it, that flag would not do anything for ts-node since it would only apply to the default REPL instance. We'd have to go ahead and implement something like nodejs/node@eeab7bc ourselves here. It should be a bit easier to do for us, but we'd need to make sure we're handling all the same things.
@blakeembrey if you run node --experimental-repl-await --eval 'require("./node_modules/ts-node/dist/bin.js")', then you set process.binding('config').experimentalREPLAwait = true via the node command line (see nodejs/node#19604). This runs our bin.ts, which then uses lib/repl.js I guess via import { start, Recoverable } from 'repl'. Thus I expect it to work automatically. I think the only one that stops this from working is the TypeScript compiler, that says top level await is invalid.
@webmaster128 I think there's a misunderstanding. All the code to handle top-level await is within defaultEval, which we don't use because we aren't evaling as JavaScript directly. Therefore we need to replicate all the logic to handle this into ts-node - the flag won't do anything since it only applies to the node.js REPL and you aren't opening the node.js REPL. The TypeScript compiler fix is the trivial part, we just ignore that diagnostic in the REPL.
I think there's a misunderstanding. All the code to handle top-level await is within defaultEval, which we don't use because we aren't evaling as JavaScript directly.
You are probably right. I was a bit too optimistic here and did not follow the core carefully enough.
Would it be possible to implement the await keyword in the ts-node REPL using the wait<T>(promise: Promise<T>): T function from above?
The TypeScript compiler fix is the trivial part, we just ignore that diagnostic in the REPL.
@blakeembrey could you elaborate on that part? I think I have a solution for running top level await in JavaScript utilizing the JavaScript AST (very similar to https://github.com/ef4/async-repl/blob/master/stubber.js and working in the ts-node REPL setup). However, I don't know how to teach typescript to pass the await untouched from TS code to JavaScript.
Top-level await seems to work at least to some extent with esm package after disabling both 1308 and 2304 diagnostics in source files. TS_NODE_IGNORE_DIAGNOSTICS=1308,2304 TS_NODE_COMPILER_OPTIONS='{"target":"es6"}' node -r esm -r ts-node/register topAwait.ts
Ignoring TS1308 is pretty simple and side-effect free since it seems to be related only to top-level await. Unfortunately TS2304 is required mainly due to error TS2304: Cannot find name 'await' and this suppresses all other 'cannot find name' errors.
It doesn't seem to work in command-line REPL though if started without a source file defined.
I don’t think we can rely on the target though, can we instead parse and wrap the file before giving to to TypeScript instead?
That would require this AST manipulation in TypeScript and not in JavaScript. I think the mayor challenge is how to expose local variables in TypeScript when rewriting:
leta=await1;a// 1
as
(async(){leta=await1;})();a// not available
what happens in the JavaScript version is that local declarations are converted to global properties (which only works in non-strict mode), e.g.
@webmaster128 That's a very good point, I didn't consider how the previous JS approach got around this issue either. Why couldn't you do AST manipulation with TypeScript though? I'm not sure myself how mature that API is, but transforms do exist as a feature.
Another question, but how would const variables be possible here? I guess we can rely on TypeScript type checking to workaround. It looks like in Chrome (and maybe node.js, haven't tested yet), it's rewritten const so it's no longer a const when using await.
Why couldn't you do AST manipulation with TypeScript though?
Didn't try it.
It looks like in Chrome (and maybe node.js, haven't tested yet), it's rewritten const so it's no longer a const when using await.
Oh, interesting. I thought they had a more sophisticated approach allowing const. The method I use does not support const either on the JS side. But TypeScript takes care of that. But I don't think that converting top level let/const to var semantics in a REPL is a big deal.
Do you have an idea for valid TypeScript that exposes variables declared in an async function body to the global scope?
Hello everyone, just stumbled on this one. Wondering if there are simple instructions on how to start a TS repl with ts-node and simply use async 100 without being hit with:
[eval].ts:1:1 - error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.
Does anyone know how node's REPL handles top-level await? Can we use their top-level await mechanism? Is it exposed as an API? Can we copy their source code?
Someone will need to propose how we can implement this.
In case it helps anyone else, I've had good results in REPL mode with node --require ts-node/register/transpile-only --experimental-repl-await (using node 14)
Reacted by Pulkit Goyal, Anthony (Trapcode), David De Anda, Camille Louédoc-Eyriès, Dan Steren, jieffyy, Jerry Baba, m.e., Danny Clarke, Daniel Pfefferkorn and 5 more
Reacted by hawkeng, Adnan Pirzada and Felipe Garrido
In case it helps anyone else, I've had good results in REPL mode with node --require ts-node/register/transpile-only --experimental-repl-await (using node 14)
It's worth noting this only helps for importing typescript files from the REPL, but you can't use any TS syntax in node REPL, ie:
Does anyone know how node's REPL handles top-level await? Can we use their top-level await mechanism? Is it exposed as an API? Can we copy their source code?
Someone will need to propose how we can implement this.
I've inspected the source and it seems it's handled here:
Activity
blakeembrey commentedon Dec 2, 2016
Off then top of my head, I don't think it's possible to support without more hacking of the TypeScript compiler and/or into the future with ES6 modules. You need an async function for await to compile, but you can't import from inside a function (it must be at the top-level). Happy to keep the issue open though.
aseemk commentedon Dec 2, 2016
Thanks for the quick reply!
Could ts-node wrap in async IIFE only if the input had no imports? (And anything else problematic?)
It could still be really valuable to support top-level
awaitfor simple cases, even if it wasn't supported for more complex ones.simonbuchan commentedon Jan 11, 2017
Maybe just
.then()before printing?Ideally do it more interactively, eg. show the prompt below the currently awaiting promises, print them as they arrive or something, but it's a start.
unional commentedon Mar 22, 2017
related topic:
https://gist.github.com/Rich-Harris/0b6f317657f5167663b493c722647221
jessedvrs commentedon Mar 28, 2018
Temporary workaround which blocks the REPL while resolving:
npm install deasync --save-dev # or yarn add deasync --devwebmaster128 commentedon Jul 20, 2018
There is top level await in the node 10+ repl and it could theoretically be utilized running
node --experimental-repl-await --eval 'require("./node_modules/ts-node/dist/bin.js")'. However, this is stopped by the TypeScript compiler complainingwhich is totally right, since top level await is not (yet?) valid JavaScript but a hack for repl convenience.
I don't see a way to get the
awaitkeyword untouched through the TypeScript compiler.webmaster128 commentedon Jul 20, 2018
Updated version of @jessedvrs's workaround that works in a type safe fashion:
blakeembrey commentedon Jul 21, 2018
@webmaster128 If I understand it, that flag would not do anything for
ts-nodesince it would only apply to the default REPL instance. We'd have to go ahead and implement something like nodejs/node@eeab7bc ourselves here. It should be a bit easier to do for us, but we'd need to make sure we're handling all the same things.webmaster128 commentedon Jul 21, 2018
@blakeembrey if you run
node --experimental-repl-await --eval 'require("./node_modules/ts-node/dist/bin.js")', then you setprocess.binding('config').experimentalREPLAwait = truevia the node command line (see nodejs/node#19604). This runs our bin.ts, which then useslib/repl.jsI guess viaimport { start, Recoverable } from 'repl'. Thus I expect it to work automatically. I think the only one that stops this from working is the TypeScript compiler, that says top levelawaitis invalid.blakeembrey commentedon Jul 21, 2018
@webmaster128 I think there's a misunderstanding. All the code to handle top-level await is within
defaultEval, which we don't use because we aren't evaling as JavaScript directly. Therefore we need to replicate all the logic to handle this intots-node- the flag won't do anything since it only applies to the node.js REPL and you aren't opening the node.js REPL. The TypeScript compiler fix is the trivial part, we just ignore that diagnostic in the REPL.webmaster128 commentedon Jul 21, 2018
You are probably right. I was a bit too optimistic here and did not follow the core carefully enough.
Would it be possible to implement the
awaitkeyword in the ts-node REPL using thewait<T>(promise: Promise<T>): Tfunction from above?webmaster128 commentedon Aug 9, 2018
@blakeembrey could you elaborate on that part? I think I have a solution for running top level await in JavaScript utilizing the JavaScript AST (very similar to https://github.com/ef4/async-repl/blob/master/stubber.js and working in the ts-node REPL setup). However, I don't know how to teach typescript to pass the
awaituntouched from TS code to JavaScript.blakeembrey commentedon Aug 10, 2018
There’s a feature in the README that allows you to ignore TypeScript diagnostic codes, it’d just be reusing that from the REPL.
2 remaining items
Pokute commentedon Aug 23, 2018
Top-level await seems to work at least to some extent with esm package after disabling both 1308 and 2304 diagnostics in source files.
TS_NODE_IGNORE_DIAGNOSTICS=1308,2304 TS_NODE_COMPILER_OPTIONS='{"target":"es6"}' node -r esm -r ts-node/register topAwait.tsIgnoring TS1308 is pretty simple and side-effect free since it seems to be related only to top-level await. Unfortunately TS2304 is required mainly due to
error TS2304: Cannot find name 'await'and this suppresses all other 'cannot find name' errors.It doesn't seem to work in command-line REPL though if started without a source file defined.
webmaster128 commentedon Aug 23, 2018
That would require this AST manipulation in TypeScript and not in JavaScript. I think the mayor challenge is how to expose local variables in TypeScript when rewriting:
as
what happens in the JavaScript version is that local declarations are converted to global properties (which only works in non-strict mode), e.g.
becomes
blakeembrey commentedon Aug 26, 2018
@webmaster128 That's a very good point, I didn't consider how the previous JS approach got around this issue either. Why couldn't you do AST manipulation with TypeScript though? I'm not sure myself how mature that API is, but
transformsdo exist as a feature.Another question, but how would
constvariables be possible here? I guess we can rely on TypeScript type checking to workaround. It looks like in Chrome (and maybe node.js, haven't tested yet), it's rewrittenconstso it's no longer aconstwhen usingawait.webmaster128 commentedon Aug 27, 2018
Didn't try it.
Oh, interesting. I thought they had a more sophisticated approach allowing const. The method I use does not support const either on the JS side. But TypeScript takes care of that. But I don't think that converting top level
let/consttovarsemantics in a REPL is a big deal.Do you have an idea for valid TypeScript that exposes variables declared in an async function body to the global scope?
feliperyan commentedon May 28, 2020
Hello everyone, just stumbled on this one. Wondering if there are simple instructions on how to start a TS repl with
ts-nodeand simply useasync 100without being hit with:[eval].ts:1:1 - error TS1378: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.ilyakatz commentedon Jul 1, 2020
Would love to know if there has been any updates to this one. Is it possible to do?
cspotcode commentedon Jul 1, 2020
@feliperyan have you tried setting options they way the error message tells you to?
guifel commentedon Oct 5, 2020
Solutions mentioned here (ignore TS error +
--experimental-repl-await) work with scripts but not in REPL mode.cspotcode commentedon Oct 5, 2020
Does anyone know how
node's REPL handles top-level await? Can we use their top-level await mechanism? Is it exposed as an API? Can we copy their source code?Someone will need to propose how we can implement this.
dansebcar commentedon Oct 6, 2020
In case it helps anyone else, I've had good results in REPL mode with
node --require ts-node/register/transpile-only --experimental-repl-await(using node 14)vamsiampolu commentedon Feb 25, 2021
I was playing around with the repl today and managed to get it working. Looking at the issue about native ESM support with ts-node helped
Environment
Node.js Version: lts/fermium (v14.16.0)
Typescript Version: 4.2.2
Configuration
package.json (just the important bit to allow treating .js as ES6 modules):
{ "type": "module", "main": "./src/index.js" }Typescript config (just the important bit):
{ "compilerOptions": { "allowSyntheticDefaultImports": true, "target": "es2017", "module" "ESNext" }, "include": ["src/**/*"] }Usage
If you are loading a typescript module that has a top level await:
To import a module within the repl, use dynamic imports.
ejose19 commentedon Jul 3, 2021
It's worth noting this only helps for importing typescript files from the REPL, but you can't use any TS syntax in node REPL, ie:
ejose19 commentedon Jul 3, 2021
I've inspected the source and it seems it's handled here:
https://github.com/nodejs/node/blob/975bbbc443c47df777d460fadaada3c6d265b321/lib/repl.js#L424-L440
https://github.com/nodejs/node/blob/975bbbc443c47df777d460fadaada3c6d265b321/lib/internal/repl/await.js#L91-L176