ts-node: Node 20: errors produced by `reportTSError` are not serialised correctly when using ESM loader
Search Terms
Node 20, reportTSError, error
Expected Behavior
When using the ts-node/esm
loader with Node 18, TS errors are reported correctly. The file below when run with node --experimental-loader ts-node/esm test.ts
const a = (b: 1 | 2) => b + 1
console.log(a(3))
produces the following correct error:
TSError: ⨯ Unable to compile TypeScript:
test.ts:3:15 - error TS2345: Argument of type '3' is not assignable to parameter of type '1 | 2'.
3 console.log(a(3))
<stack trace>
diagnosticCodes: [ 2345 ]
Actual Behavior
On Node 20, the error is serialised to only retain diagnosticCodes
and no other info
node:internal/process/esm_loader:42
internalBinding('errors').triggerUncaughtException(
^
{ diagnosticCodes: [ 2345 ] }
Likely related to https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V20.md#custom-esm-loader-hooks-run-on-dedicated-thread
Steps to reproduce the problem
Run command above on Node 20
Minimal reproduction
https://github.com/TypeStrong/ts-node-repros/pull/33
https://github.com/TypeStrong/ts-node-repros/actions/runs/5057165129/jobs/9075579140
Specifications
- ts-node v10.9.1
- node v18.16.0
- TS compiler v5.0.4
and
-
ts-node v10.9.1
-
node v20.2.0
-
TS compiler v5.0.4
-
package.json:
{
"type": "module",
}
- Operating system and version: MacOS 13.4
- If Windows, are you using WSL or WSL2?:
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 29
- Comments: 27
I am using ts-node this way on Node 20 and works. Config
["type": "module"]
in package.json, then update tsconfig.json to ts-node’s ESM support and pass the loader flag to node in scripts,node --loader ts-node/esm ./index.ts
. tsconfig.json:The world was not ready for ESM 7 years ago and isn’t yet quite ready
@cspotcode Node 20.4 is out now with the serialisation fix in place. Could you please release a new version of
ts-node
so it can be used like this?node --loader=ts-node/esm --import=./logError.js
Unless there’s another way of getting this to work that doesn’t require the fix in #2025.
@cspotcode serialisation has been fixed in the nightly version of Node. The problem is that
internalBinding('errors').triggerUncaughtException(
ignoresSymbol(nodejs.util.inspect.custom)
but withprocess.setUncaughtExceptionCaptureCallback(console.error)
the type error info is shown again in the console.I also get this when any TSC error happens under the hood:
The command is
ts-node v 10.9.1
Thanks @danrr.
This worked for me on node v20.6.1
Full command:
node --test --no-warnings=ExperimentalWarning --loader ts-node/esm --import=./logError.js test-*.ts
OK, I understand, so it’s the correct exception, hence the
{ diagnosticCodes: [ 2345 ] }
output but thediagnosticText
is being lost in the serialisation of the exception.I had a look at the code in https://github.com/nodejs/node/blob/main/lib/internal/error_serdes.js and I think I see what’s happening.
The
if
on line https://github.com/nodejs/node/blob/main/lib/internal/error_serdes.js#LL119 is skipped as the type is[object Object]
, not[object Error]
.The
if
on line https://github.com/nodejs/node/blob/main/lib/internal/error_serdes.js#L137 is skipped becauseObjectPrototypeHasOwnProperty(error, customInspectSymbol)
returnsfalse
– the check here is done usingObject.prototype.hasOwnProperty
but, because the error is created fromTSError
, theINSPECT_CUSTOM
is a property of the prototype of the error, not of the error itself. .This means it falls into the try block here https://github.com/nodejs/node/blob/main/lib/internal/error_serdes.js#LL145 which only serialises the
diagnosticCodes
(as that’s the only own property on theTSError
).The fallback on https://github.com/nodejs/node/blob/main/lib/internal/error_serdes.js#LL150 actually serialises it correctly but it’s never reached normally as the block above returns.
I tried to change TSError to move
to the constructor, which caused it to serialise correctly and deserialise to an object with the inspect output of TSError as its inspect output. BUT the output from node is now this:
from this line https://github.com/nodejs/node/blob/main/lib/internal/process/esm_loader.js#L42.
I even put
console.log("loadESM", err)
on the line above the call tointernalBinding('errors').triggerUncaughtException
and that printed the TSError output correctly, so I’m not sure what is happening.I tried to set
process.setUncaughtExceptionCaptureCallback()
but I think I ran into https://github.com/TypeStrong/ts-node/issues/2024.Any idea what’s happening with this?
Took me about 20 minutes to come with this solution:
ESM + TypeScript + node is definitely quite tiresome combination 😅
@Pyrolistical your solution doesn’t work for me, I get the “Error [ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE]: The
domain
module is in use, which is mutually exclusive with calling process.setUncaughtExceptionCaptureCallback()”Would it be possible to log the error from reportTSError instead of just throwing it? In my case it didn’t even produce the diagnosticCodes or a stack trace or even a line where the error occurred. The ts-node process just hang up and printed
It took some non-trivial amount of effort to figure that the problem is related to this bug report.
Put this patch in
patches/ts-node+10.9.1.patch
(you might need to correct the version in file name)Add to
package.json
scripts
this:It will auto-apply patch on
npm install
Do
npm istall
. That’s it, patch applied.For anyone who just wants to see what the TSC errors are without any fanciness, here’s how I patched ts-node’s
TSError
impl:Now I can see the error:
You can use https://github.com/ds300/patch-package for replicating this workaround locally.
Thanks for filing this issue! I just wanted to mention that it makes debugging the tests running with Node.js’s Test Runner (
node --loader=ts-node/esm --test ./src/tests.ts
, from secutils-web-scraper) incredibly painful, as it is common to rely on errors while writing tests. One has to switch to Node.js 18 just to see the actual error.I had this error with node 21.7.1 instead of real error:
and i started to get correct error messages after adding this to tsconfig.json (found solution from this thread):
"transpileOnly": true,
This is great, thanks for the patch, @alpharder !
Unfortunately, I couldn’t get your patch working as-is.
Do you think that you could also:
diff
and then add-
and+
lines to the lines that have been removed and added? A bit nicer to be able to check the patch…