How to import esm modules with NodeJS 13, and Typescript 3.8? - node.js

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"
}

Related

Typescript and Subpath Imports

I am trying to get Node subpath imports and typescript to work. My IDE has no problem resolving the imports, but Typescript is never happy.
Github repo with code: https://github.com/doronrosenberg/ts-subpath-imports.
package.json:
"imports": {
"#internal/*": "./internal/*.ts",
"#internal2": "./internal"
}
tsconfig.json:
"paths": {
"#internal/*": "./internal/*.ts",
"#internal2": ["./internal"]
}
and the code:
import { foo } from "#internal/index";
import { bar } from "#internal2";
No matter how I set things up, I always get:
src/test.ts:1:21 - error TS2307: Cannot find module '#internal/index' or its corresponding type declarations.
1 import { foo } from "#internal/index";
~~~~~~~~~~~~~~~~~
src/test.ts:2:21 - error TS2307: Cannot find module '#internal2' or its corresponding type declarations.
2 import { bar } from "#internal2";
~~~~~~~~~~~~
Any ideas?
After hacking around with Typescript for a few weeks, I got a working solution.
Let's say I have a package called #kodadot1/metasquid with multiple submodules (consolidator and entity).
In my package.json I declare imports
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./consolidator": {
"types": "./dist/consolidator.d.ts",
"import": "./dist/consolidator.mjs",
"require": "./dist/consolidator.cjs"
},
"./entity": {
"types": "./dist/entity.d.ts",
"import": "./dist/entity.mjs",
"require": "./dist/entity.cjs"
}
}
The trick is to create a .d.ts file for each submodule in the project's root.
So for submodule entity I will make a file called entity.d.ts that contains
export * from './dist/entity'
Now to publish it correctly in npmjs extend your package.json like:
"files": [
"dist",
"*.d.ts"
]
Now just publish, and you can enjoy subpath imports:
import { get } from '#kodadot1/metasquid/entity'
Whole code is available here
The support of subpath exports requires newer module resolutions such as Node16 and NodeNext:
{
"compilerOptions": {
"moduleResolution": "Node16" // or `"NodeNext"`
}
}

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

Creating a custom visual for PowerBI in NodeJS - "Cannot find name 'IVisualHost'

I'm trying to follow this tutorial on creating a custom visual for Power BI : https://learn.microsoft.com/en-us/power-bi/developer/custom-visual-develop-tutorial
The test with the default code works properly when I connect to Power BI Cloud, as shown in the part"Testing the custom visual" step 8, of the tutorial.
The problem is when I try to add the class-level properties in the visual.ts file (after I deleted the code as indicated in the part "Developing the visual elements" step 2 of the tutorial ), I get the error "Cannot find name 'IVisualHost'".
"use strict";
import "core-js/stable";
import "./../style/visual.less";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import VisualObjectInstance = powerbi.VisualObjectInstance;
import DataView = powerbi.DataView;
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
import { VisualSettings } from "./settings";
export class Visual implements IVisual {
private host: IVisualHost; ------------ the first error is here
private svg: d3.Selection<SVGElement>;
private container: d3.Selection<SVGElement>;
private circle: d3.Selection<SVGElement>;
private textValue: d3.Selection<SVGElement>;
private textLabel: d3.Selection<SVGElement>;
constructor(options: VisualConstructorOptions) {
this.svg = d3.select(options.element) ----------- the second error is here
.append('svg')
.classed('circleCard', true);
this.container = this.svg.append("g")
.classed('container', true);
this.circle = this.container.append("circle")
.classed('circle', true);
this.textValue = this.container.append("text")
.classed("textValue", true);
this.textLabel = this.container.append("text")
.classed("textLabel", true);
}
I also have this other error in the same visual.ts file : 'd3' refers to a UMD global, but the current file is a module. Consider adding an import instead. I imported the library with the commands : "npm i d3#3.5.5 --save" and "npm i #types/d3#3.5"
This is my pbiviz.json file :
{
"visual": {
"name": "visual9basic",
"displayName": "visual9basic",
"guid": "visual9basic252E75AF09794C8F8CE14414674FBC3E",
"visualClassName": "Visual",
"version": "1.0.0",
"description": "",
"supportUrl": "",
"gitHubUrl": ""
},
"apiVersion": "2.6.0",
"author": {
"name": "",
"email": ""
},
"assets": {
"icon": "assets/icon.png"
},
"externalJS": [
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
"node_modules/d3/d3.min.js"
],
"style": "style/visual.less",
"capabilities": "capabilities.json",
"dependencies": null,
"stringResources": []
}
Here is my tsconfig.json :
{
"compilerOptions": {
"allowJs": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"outDir": "./.tmp/build/",
"moduleResolution": "node",
"declaration": true,
"lib": [
"es2015",
"dom"
]
},
"files": [
"node_modules/powerbi-visuals-utils-dataviewutils/lib/index.js",
"./src/visual.ts",
"./src/settings.ts",
]
}
In Visual.ts, try adding the following import:
import IVisualHost = powerbi.extensibility.IVisualHost;
It should take care of the missing interface reference.
For the d3 reference you can try
import * as d3 from "d3";
Hope this helps. I haven't worked through the tutorial myself yet.
What I found out is that after a certain patch of npm, the ".api" folder isn't created any more. Instead, those files are stored under "node_modules/powerbi-visuals-api/". Also, the file "index.d.ts" replaces the former "PowerBI-visuals.d.ts".
But, I still have the 2 errors I described.

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.

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

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"
]
}

Resources