Compile time errors being suppressed when using esm
I really struggled to think of the correct title for this, so please edit it as required. I went down a rabbithole today whilst moving one of our apps to esm, as it was failing to start (with ts-node) due to the following node error:
❯ node --experimental-specifier-resolution=node --loader ts-node/esm lib/index.ts
node:internal/modules/run_main:115
triggerUncaughtException(
^
[Object: null prototype] {
[Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]]
}
Node.js v22.5.1
Doesn't really give away much. It turned out however that the root caused we missed a return type on a promise eg Promise needed to be Promise<void>. This error was visible if you did a tsc, but ts-node was giving this output.
After gradually chipping away at the code to make a minimal example; i managed to reproduce it with a really simple bit of code that doesn't even technically have an error in it.
Assuming your code is in ./lib, create ./lib/index.ts:
import { SomeServer } from './server.js'
const webServer = new SomeServer()
and ./lib/server.ts:
export class SomeServer {}
If you try and start this; you'll get the error. The only thing "wrong" with this code is the fact that webServer is an unused variable. If i add console.log(webServer), eg:
import { SomeServer } from './server.js'
const webServer = new SomeServer()
console.log(webServer)
Then all is well:
❯ node --experimental-specifier-resolution=node --loader ts-node/esm lib/index.ts
SomeServer {}
Specifications
- versions:
❯ ./node_modules/.bin/ts-node -vv
ts-node v10.9.2
node v22.5.1
compiler v5.3.3
- tsconfig.json:
{
"compilerOptions": {
"lib": ["ES2022", "DOM"],
"module": "ES2022",
"target": "ES2022",
"rootDir": "./",
"outDir": "./built",
"baseUrl": "./",
"allowJs": false,
"checkJs": false,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noEmitOnError": false,
"sourceMap": false,
"declaration": false,
"strictBindCallApply": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
"incremental": false,
"skipDefaultLibCheck": true,
"experimentalDecorators": true,
"noUnusedLocals": true,
"esModuleInterop": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
},
"include": ["lib/**/*.ts", "test/**/*.ts"],
"exclude": ["node_modules/**/*"]
}
- Operating system and version: Mac
Just a heads up that my error was due to the server.ts file having a variable in an "if" check which I hadn't initialized yet, e.g.
app.get("/hut/:locationId", (req: Request, res: Response) => {
const data = req.params.locationId;
if (!location_id) { // <---- location_id was not not instantiated anywhere
res.status(400).send("Bad Request: Missing locationId");
} else {
res.render('hut_template', data);
}
});
The reason my tsc compilation wasn't catching was due to the same variable name "location_id" being instantiated in another .ts file so I was going mad to search for the cause...
I encountered this error when there was no use of a defined variable or function, it happens when I execute the script using ts-node, let's say node --loader ts-node/esm scripts/myscript.ts.
And then I did some research and tried a simple trick, and the error went away.
// the linter will help you with this
const someVar: any // intended to use later, I want to check it out first
//
const _falseCall = (_any: any) => { }; _falseCall(someVar);
// what about this
// You must use this function or it will trigger errors
const myFutureFunc = ()=> {
// ...
// lines of codes
// ...
}
//
_falseCall(myFutureFunc); // I tried this and the error went away
// ...
// tons of lines
// ...
here is my .eslintrc.cjs:
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
To conclude, if I know which variables or functions are not used, I try to comment out on them.
However, there is a suggestion to enable type-aware lint rules, check it out here.
I don't know if it helps.
This seems to be a general issue of how any UncaughtException is shown when ts-node/esm processes a file. It makes logs checking impossible as it always ends up showing the same cryptic result:
node:internal/modules/run_main:123
triggerUncaughtException(
^
[Object: null prototype] {
[Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]]
}
The workaround to fix this is to make ts-node log TS errors to stderr instead of throwing exceptions (credits to this comment):
/* tsconfig.json */
{
/* ... */
"ts-node": {
"logError": true,
"pretty": true /* <= technically not required */
}
}
@snowbytes this solution is working for me, thanks 👍