How have IDE type awareness with app-module-path in Visual Studio Code - node.js

I'm in a node environment where they are using app-module-path so that the requires can always be written from a base path.
So all path's can be: const something = require('/api/something') instead of having to back down the folder structure with something like: const something = require('../../api/something).
Without the exact path, VSCode says all types are of type any. This means functionality like 'go to definition' won't work.
Is there a way to configure VSCode to address this?

After some more searching I found this can be done by using a jsconfig.son file with the following settings.
{
"compilerOptions": {
"target": "ES6",
"allowSyntheticDefaultImports": true,
"jsx": "react",
"module": "commonjs",
"baseUrl": ".",
"paths": {
"*": [
"*",
"app/*"
]
}
},
"include": [
"app/**/*.js"
]
}

Related

Use of alias paths inside a referenced project

Since TypeScript 3, it is possible to use projects as references within a given project.
I was trying to use such feature on two personal projects I have but it was not working. So I decided to use a given example as a starting point: https://github.com/appzuka/project-references-example
When I launch npm run build, all projects compile successfully. I then decided to modify the tsconfig.json file of package animals to include the support of aliases. The file was modified as such:
{
"extends": "../../tsconfig-base.json",
"compilerOptions": {
"outDir": "lib",
"rootDir": ".",
"baseUrl": ".",
"paths": { "#animals/*": [ "*" ] }
},
"references": [
{ "path": "../core" }
]
}
(baseUrl and paths properties were added inside compilerOptions)
I then modified the first import of dog.tsx file this way:
import { Animal, Size } from #animals/animal;
When I try to build again the main project, it fails. The error is:
ERROR in C:\Users\User\Documents\project-references-example\packages\animals\dog.tsx
[tsl] ERROR in C:\Users\User\Documents\project-references-example\packages\animals\dog.tsx(1,30)
TS2307: Cannot find module '#animals/animal' or its corresponding type declarations.
To make it working, I need to add the alias also on the main project, like this (in tsconfig.json):
"paths": {
"#animals/*": [ "packages/animals/*" ]
}
Is there a possibility to use alias in a referenced project without having to specify such alias in the parent project?
Thank you!

How to import esm modules with NodeJS 13, and Typescript 3.8?

I have a problem with some imports in NodeJS. I want to use the new features of Typescript 3.8, like private fields : #myPrivateField
I don't know how to correctly import the module "typescript" in my class. I tried many options, but impossible to solve my problem.
My files :
package.json
{
"name": "test",
"scripts": {
"start": "tsc && node --experimental-modules --es-module-specifier-resolution=node main.js"
},
"dependencies": {
"#types/node": "^13.13.2",
"app-root-path": "^3.0.0",
"fs-extra": "^9.0.0",
"tsutils": "^3.17.1"
},
"devDependencies": {
"ts-node": "~8.3.0",
"typescript": "^3.8.3"
},
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"lib": [
"ESNext",
"es2016",
"dom",
"es5"
],
"module": "esnext",
"moduleResolution": "Node",
"sourceMap": true,
"target": "es6",
"typeRoots": [
"node_modules/#types"
]
}
}
main.ts
// import ts = require("typescript");
import * as ts from "typescript";
export class Main {
node: ts.Node;
#test = 'zzz';
constructor() {}
process(): void {
ts.forEachChild(this.node, function cb() {
});
console.log('#test', this.#test);
}
}
const main = new Main();
main.process();
With this code, when I run npm run start, I have the error TypeError: ts.forEachChild is not a function
Without the line with ts.forEachClid() it logs correctly the value of the private field #test.
If I try to replace import * as ts from "typescript"; by import ts = require("typescript");, I have the error TS1202: Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead
Of course, I tried many changes in tsconfig.json and in package.json (with `"type" = "module"), but impossible to solve this problem.
I even tried to replace "module": "esnext" by "module": "commonjs", but I have an error exports is not defined.
Remark :
This is not specific to the module "typescript". I have the same problem with other modules like "fs-extra", which are making exports in a different way than most of classic NodeJS modules.
For example, the module "typescript" exports with export = ts.
I found this reference too, but it didn't help me a lot :
https://www.typescriptlang.org/docs/handbook/modules.html
My nodeJs version is 13.3.0 and my typescript version is 3.8.3
Thanks for your help
Finally, the good response was : you need commonjs and es2018 to be able to use the typescript #privateFields in a node module.
Here is the correct tsconfig.json to use :
{
"compileOnSave": false,
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "es2018",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2018",
"dom"
]
}
}
ESM Modules are still in experimental mode, there are a lot of modules that do not support it, e.g. uses module.exports (CommonJS) instead of export (ESM).
Your best choice here is to use commonjs module and run node without any flags. Also, type of the package in package.json must be commonjs as well.
Answering your question "How to import ESM module"... In case, module does support ESM modules, you can just use import as you usually do:
import { something } from './something';
UPD: as OP author mentioned, for private fields to work, there is must be set a target es2018. The reasoning behind this is that private fields is not a part from ES2015 specification and you need to upgrade to the minimal supported target to do that.
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "es2018"
}

Cannot get node or ts-node/register to use es2020 anymore (flatMap is not a function)

I am unable to use flatmap with node or ts-node despite previously being able to in my code. I am not sure what changed but I am getting this error 'TypeError: [x].flatMap is not a function' flatMap() should be a function because I explicitly make x an array (this code was working before no problems).
here is my tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"target": "es2020",
"esModuleInterop": true,
"sourceMap": true,
"strictNullChecks": true,
"outDir": "out",
//"strictBindCallApply": true,
//"strict": true
}
}
and here is my lancher
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/src/main.ts",
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: build",
"runtimeArgs": ["-r", "esm"],
"stopOnEntry": true,
"console": "integratedTerminal",
},
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"--require",
"esm",
"--require",
"ts-node/register",
"--project",
"${workspaceFolder}/tsconfig.json",
"${workspaceFolder}/**/*spec.ts"
],
"console": "integratedTerminal",
"stopOnEntry": false,
}
]
}
and the flatmap call in question
(x: discord.Message | discord.Message[] | null): (discord.Message | null)[] => [x].flatMap(
// #ts-ignore
(t: discord.Message | discord.Message[] | null): (discord.Message | discord.Message[] | null) => t
)
another example that does not work:
const test = [1,3,4,[5,4,2]].flatMap(x => x);
TypeError: [1,3,4,[5,4,2]].flatMap is not a function
Neither ts-node/register or ts-node seems to respect "target": "es2020" anymore and I am not sure what exactly changed. VScode does have context for intellesense for flatMap. Can anyone explain to me why it is not working anymore?
The problem was I had a conflict in the version number of node after an install of a program. I have stopped using the snap package of node and started using nvm.
I had the same issue. I combed through many forums. Could not fix. The below fixed for me. This is one more thing that you can try.
My node version was 14.19.
I remember the same code working sometime back. So I tried to run it on version 12.
I had nvm installed before. I switched my node version to 12 using command nvm use 12. Surprisingly no error with this version.
So I tried updating my node version by running nvm install 14 which updated my version from 14.19 to 14.21. Now when I test, it worked fine.
NVM is a very useful tool when such issues occur. Remember to keep the version match
Thanks
Narayanan

How to add subdirectory module aliases in tsconfig?

I can set module aliases with no issue:
"baseUrl": "./app",
"paths": {
"assets/*": ["assets/*"],
"components/*": ["components/*"],
...
},
This works. However, I (actually my client, who wants to extract some parts of the project as a module in the future) want to create subdirectories as aliases to different paths (assume that all the directories as values exist, and I cannot change the actual directory structure):
"paths": {
"assets/*": ["assets/*"],
"components/*": ["components/*"],
"#myapp/api/*": ["services/myapp/*"],
"#myapp/state/*": ["state/*"],
},
Putting aside whether this is a good pattern/practice or not (as I'm asked to do it this way), is this technically possible without touching the physical directory structure (and without the use of 3rd party dependencies)? (I'm using Typescript 3.4.3 and Vscode 1.33.1)
"baseUrl": "./",
"paths": {
"#myapp/environment": [ "./src/environments/environment" ],
"#myapp/extensions": [ "./src/extensions" ],
"#myapp/testing": [ "./src/testing" ],
"#myapp/constants": [ "./src/app/shared/constants" ],
"#myapp/data": [ "./src/app/data" ],
"#myapp/shared": [ "./src/app/shared" ],
"#myapp/core/*": [ "./src/app/core/*" ],
"#myapp/*": [ "./src/app/*" ]
}
this works like a charm on my project

Typescript dynamic import() of file only works with hardcoded strings

I am trying to dynamically load a file in typescript using import(). I have managed to get it working if I hardcode the path to the file, as this:
import("./file").then(module => doStuff(module)); // Success
But as soon as I store the path in a variable, it will not find the file.
This will succeed to compile but will at runtime complain: Cannot find module "./file".
path = "./file";
import(path).then(module => doStuff(module)); // Cannot find module "./file".
Is there any way to fix this, or any way to work around for the issue?
tsconfig.json:
{
"compilerOptions": {
"sourceMap": true,
"target": "es6",
"module": "esnext",
"strict": true,
"allowJs": true,
"lib": [
"esnext"
],
"moduleResolution": "node"
},
"exclude": [
"node_modules"
]
}
Yup, this is an interesting one lol
prefix your import with '../../' or similar (play around with it)
e.g. import(../../${m}.ts)
Only way to do it as the issue is with the import itself and not typescript or others.

Resources