node --import=./tsnode.esm.js --enable-source-maps app.ts
Gives:
node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
^
Error: Cannot find module '/Users/aarhus/code/introdus/backend/dist/src/common/auth/auth.js' imported from /Users/aarhus/code/introdus/backend/app.ts
at finalizeResolution (/Users/aarhus/code/introdus/backend/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:366:11)
at moduleResolve (/Users/aarhus/code/introdus/backend/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:801:10)
at Object.defaultResolve (/Users/aarhus/code/introdus/backend/node_modules/ts-node/dist-raw/node-internal-modules-esm-resolve.js:912:11)
at /Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:218:35
at entrypointFallback (/Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:168:34)
at /Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:217:14
at addShortCircuitFlag (/Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:409:21)
at resolve (/Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:197:12)
at nextResolve (node:internal/modules/esm/hooks:865:28)
at Hooks.resolve (node:internal/modules/esm/hooks:303:30)
at /Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:218:35
This is a little concerning as it shouldn't be pointing to the dist directory, if I'm trying to run using ts-node, should it? Worst case, it should be at least compiling the code before attempting to run it.
This happens regardless if I have transpileOnly
to true or false.
> node --import=./tsnode.esm.js --enable-source-maps app.ts
(node:2688) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
[DEV] Using ethereal mail transport
[DEV] Using ethereal mail transport
Error: Redis is already connecting/connected
at /Users/aarhus/code/introdus/backend/node_modules/ioredis/built/Redis.js:107:24
at new Promise (<anonymous>)
at EventEmitter.connect (/Users/aarhus/code/introdus/backend/node_modules/ioredis/built/Redis.js:103:25)
at <anonymous> (/Users/aarhus/code/introdus/backend/dist/src/common/helpers/db.js:32:6)
at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
Error: Redis is already connecting/connected
at /Users/aarhus/code/introdus/backend/node_modules/ioredis/built/Redis.js:107:24
at new Promise (<anonymous>)
at EventEmitter.connect (/Users/aarhus/code/introdus/backend/node_modules/ioredis/built/Redis.js:103:25)
at <anonymous> (/Users/aarhus/code/introdus/backend/src/common/helpers/db.ts:43:3)
at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
/Users/aarhus/code/introdus/backend/node_modules/mongoose/lib/index.js:587
throw new _mongoose.Error.OverwriteModelError(name);
^
OverwriteModelError: Cannot overwrite `notificationSchema` model once compiled.
at Mongoose.model (/Users/aarhus/code/introdus/backend/node_modules/mongoose/lib/index.js:587:13)
at <anonymous> (/Users/aarhus/code/introdus/backend/src/common/helpers/mongooseModels.ts:3:39)
at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
at async loadESM (node:internal/process/esm_loader:34:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
Cleaning up the tsconfig.json
to the following settings:
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 20",
"compilerOptions": {
/* Basic Options */
"target": "ESNext",
"module": "NodeNext",
"lib": [
"ESNext"
],
"allowJs": true /* Allow javascript files to be compiled. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
"outDir": "dist" /* Redirect output structure to the directory. */,
"removeComments": true /* Do not emit comments to output. */,
"importHelpers": true /* Import emit helpers from 'tslib'. */,
"isolatedModules": true,
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
/* Module Resolution Options */
"moduleResolution": "Node16",
"baseUrl": "./",
"paths": {
"#src/*": ["src/*"],
"#reports/*": ["reports/*"]
},
// "rootDirs": [],
"typeRoots": [
"./types"
],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
/* Source Map Options */
"sourceRoot": "./"
"inlineSources": true /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */,
/* Experimental Options */
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
/* Advanced Options */
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"ts-node": {
"esm": true,
"swc": true,
"transpileOnly": true
},
"exclude": [
"node_modules",
"**/*.spec.ts",
"dist",
"logs",
"app.js/",
"gulpfile.js",
"test"
]
}
This gives the desired settings for module
and moduleResolution
making it seem like the most correct solution. This solution also attempts to use swc
to compile files, with ts-node
.
However, this approach results in an error with .json
imports, that my visual studio code is not showing. Only the compiler gives this error:
node:internal/process/esm_loader:40
internalBinding('errors').triggerUncaughtException(
^
TypeError [Error]: Module "file:///Users/aarhus/code/introdus/backend/src/common/auth/helpers/whitelist.json" needs an import attribute of type "json"
at validateAttributes (node:internal/modules/esm/assert:89:15)
at defaultLoad (node:internal/modules/esm/load:150:3)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async /Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:255:39
at async addShortCircuitFlag (/Users/aarhus/code/introdus/backend/node_modules/ts-node/src/esm.ts:409:15)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async Hooks.load (node:internal/modules/esm/hooks:448:20)
at async handleMessage (node:internal/modules/esm/worker:196:18) {
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}
However it is being much faster, displaying the error in about ~0.5s, which is much more aligned with the expected execution speed.
The above error is only produced, when using the following script to run the application: "start": "node --import=./tsnode.esm.js --enable-source-maps app.ts",
.
If I instead use the script node --import=ts-node/esm --watch ./app.ts
, it produces another error:
(node:41864) ExperimentalWarning: Watch mode is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
TypeError: Unknown file extension ".ts" for /Users/aarhus/code/introdus/backend/app.ts
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
at defaultLoad (node:internal/modules/esm/load:141:22)
at async ModuleLoader.load (node:internal/modules/esm/loader:409:7)
at async ModuleLoader.moduleProvider (node:internal/modules/esm/loader:291:45)
at async link (node:internal/modules/esm/module_job:76:21) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
Failed running './app.ts'
Walking further down this line and fixing the JSON import errors, actually enabled me to boot the local server, using npm run start
(see script above). Great!
However, typegoose
is now throwing a ton of Setting "Mixed" for property "FastTrackConfiguration.language"
errors that weren't previously there. These errors aren't supposed to show, but seems to be an issue when trying to set a typegoose property equal to a typescript type
, instead of a native one (String
, Boolean
Number
etc.).
- Running
npm run start
(after compiling) takes 60+ seconds before it does anything tsconfig.json
gives an error ifmodule
is set to ESNext. I'm only seeing this error, using Typescript 5.4.3, not my previous 5.1 version- However, if I change
module
toNode16
, the JSON imports are flagged with an error that in order to have JSON imports, I must usenodenext
.
- However, if I change
- After setting the above settings (
module
andmoduleResolution
toNode16
) I'm getting errors when importing types, even from external libraries.- A first it was the following:
import { MessageTypes } from "../../message/message.config.js";
^
SyntaxError: The requested module '../../message/message.config.js' does not provide an export named 'MessageTypes'
but fixing that is giving me generic errors, like this one:
import { modelOptions, prop, Ref } from "@typegoose/typegoose";
^
SyntaxError: The requested module '@typegoose/typegoose' does not provide an export named 'Ref'`
- If I set
moduleResolution
tobundler
, I'm getting an error on JSON imports: `Dynamic imports only support a second argument when the '--module' option is set to 'esnext', 'node16', or 'nodenext'.ts(1324)´.
- I should try to use a different tsconfig base: https://github.com/tsconfig/bases
- I should probably try cleaning up the tsconfig, so I can set the values I prefer (node16/nodenext)
- Try to follow this guide: https://gist.github.com/slavafomin/cd7a54035eff5dc1c7c2eff096b23b6b
- Perhaps try a new branch from base conversion (after all imports are fixed)
- Walk through common errors for
ts-node
: https://github.com/TypeStrong/ts-node#common-errors - Follow this guide and make sure I have followed steps carefully: https://dev.to/a0viedo/nodejs-typescript-and-esm-it-doesnt-have-to-be-painful-438e
- A new branch with an incremental pace of changes, so that I'm closer to working state might be ideal.
- Enable import maps in
package.json
- Make Redis an exported singleton (OPTIONAL)
- Replace
ts-node-dev
withts-node
+swc
, asts-node-dev
won't work with ESM. - Switch to
Node16
orNodeNext
intsconfig.json
- Enable import maps in
Lastly, its worth investigating why I wanted to transfer to ESM in the first place. One is, it is the where the ecosystem is moving, so may as well get ahead of the change. Another is that it (hopefully) can simplify the DX and the tooling used. Third is that by simplifying tools, we'll get better errors and more easily able to integrate with reporting tools such as Sentry (which has proved difficult in the past).