How to use node --experimental-modules with Typescript output - node.js

Might be the stupidest question ever, but I have a Node project that is using ES modules with --experimental-modules and Node 12.
Now I'm adding an inner package to the monorepo that's written in Typescript that's consumed by the main node app. I'm struggling with my Typescript build settings that produce something that will work with --experimental-modules.
Currently tsconfig.json:
{
"include": [
"src/**/*ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
],
"compilerOptions": {
"module": "esnext",
"esModuleInterop": true,
"target": "esnext",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"lib": ["es2015"]
}
If I have index.ts that imports from a neighbour ts file:
import { schema } from './schema'
The built import statements is without any .js and this makes my node app choke:
(node:78972) ExperimentalWarning: The ESM module loader is experimental.
internal/modules/esm/default_resolve.js:79
let url = moduleWrapResolve(specifier, parentURL);
^
Error: Cannot find module /Users/viktor/dev/projects/kb-frontend/packages/graph/dist/schema imported from /Users/viktor/dev/projects/kb-frontend/packages/graph/dist/index.js
The reason is that the import is without .js - patching that in my dist directory of the ts build it works.
I cannot simply change my ts module to commonjs since this will also change the way my main exports are working with my esm based main giving me other errors:
import { server as graphMiddleware } from '#kb-front/graph'
^^^^^^
SyntaxError: The requested module '#kb-front/graph' does not provide an export named 'server'
I would not want to add a lot of .default here and there since the entire setup I have is to avoid commonjs alltogether and just use js and typescript with esm. Hey, I want the new shiny stuff.
What am I missing?

Two issues on using native module support in browser with Typescript:
https://github.com/microsoft/TypeScript/issues/13422
https://github.com/microsoft/TypeScript/issues/16577
From them I got the hack to just import with .js extension. Super weird, but works for my case.

Related

NPM Package: Conflicts between ES libraries

I am currently trying to split a monorepo which includes the API and all the front-end stuff into different repo (at least have the API appart).
To achieve this, I had to first split the common and angular core libraries into two privates NPM packages. I manage to handle this easily for the angular core library using the CLI.
For the API (express 4.17.2/NodeJS 14.16/TypeScript 4.6), I'm not able to compile properly because of some ES conflicts. It's been two days I've been searching everywhere and the ES configuration seems to be very obscure because I don't find any related documentation.
I tried to follow this example to compile with pure ESM package without success. The first commend explain well the struggle.
Here is my problem:
I did a library named common-lib, it contains mainly services, interfaces and constants...
This library is exported from api/node_modules/#project/project-common-lib/dist/#project/project-common-lib/index.js
export * as constants from './constants/index.js';
export * as enums from './enums/index.js';
export * as interfaces from './interfaces/index.js';
export * as services from './services/index.js';
and installed into the api as an NPM package.
The package.json of the library contains this related information
"exports": "./dist/#project/project-common-lib/index.js",
"types": "./dist/#project/project-common-lib/index.d.ts",
"engines": {
"node": ">=14.16"
},
"type": "module",
And finally, this is my tsconfig.json:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"allowJs": true,
"removeComments": true,
"resolveJsonModule": true,
"typeRoots": [
"./node_modules/#types"
],
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": [
"esnext"
],
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "Node",
"skipLibCheck": true,
},
"include": [
"src/**/*"
],
"exclude": ["node_modules"],
}
Once I run npx ts-node-esm src/main.ts I get this error:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /home/project/project-api/node_modules/#project/project-common-lib/dist/#project/project-common-lib/index.js
require() of ES modules is not supported.
require() of /home/project/project-api/node_modules/#project/project-common-lib/dist/#project/project-common-lib/index.js from /home/project/project-api/dist/app/models/adminUser.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /home/project/project-api/node_modules/#project/project-common-lib/package.json.
I tried to remove the "type": "module" and to rename the index.js to .csj without any success.
I've come to a frustrating point where I can't make it work trying different configuration not documented so not really knowing what I'm doing.
For example I tried to change the ts compiler options to es5, es6... the "type" to "commonjs"... I have tried many things and I always end up with an error.
One way I get the above error, the other way removing the "type": "module" I get this error:
export * as constants from '#project/project-common-lib/dist/#project/project-common-lib/constants/index.js';
^^^^^^
SyntaxError: Unexpected token 'export'
Can someone clarify me how is it supposed to work ?
I don't use any es5 import const ... require (''). All the API use ES6 syntax with import ... from ...
How can it be so difficult to export/import some basic constants, services and interfaces ?

Typescript/Node Error [ERR_MODULE_NOT_FOUND]: Cannot find module

Converting Project form CJS to ESM
I am attempting to convert my current TypeScript-Node project from ESM to CJS, however, I keep getting the error below
Error [ERR_MODULE_NOT_FOUND]: Cannot find module` 'redacted/dist/config/datadog'
imported from /redacted/dist/app.js
This is what the import looks like in app.ts:
import './config/datadog';
And this is what it looks like for app.js
import './config/datadog';
Here is my datadog.ts document
datadog.ts
import tracer from 'dd-trace';
tracer.init({
logInjection: true,
profiling: true,
appsec: true
});
export default tracer;
Here is the full printout of the error I am recieving when I execute the app via ~/$ node dist/app.js.
> node dist/app.js
node:internal/errors:465
ErrorCaptureStackTrace(err);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'redacted/dist/config/datadog' imported from /redacted/dist/app.js
at new NodeError (node:internal/errors:372:5)
at finalizeResolution (node:internal/modules/esm/resolve:405:11)
at moduleResolve (node:internal/modules/esm/resolve:966:10)
at defaultResolve (node:internal/modules/esm/resolve:1176:11)
at ESMLoader.resolve (node:internal/modules/esm/loader:605:30)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:318:18)
at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
at link (node:internal/modules/esm/module_job:78:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
Node.js v18.0.0
Process finished with exit code 1
It works fine When running using ts-node
node --experimental-specifier-resolution=node --loader ts-node/esm app.ts --project tsconfig.json
I have configured my tsconfig.json file like this:
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "node",
"esModuleInterop": true,
"rootDir": "./src",
"outDir": "./dist",
"forceConsistentCasingInFileNames": true,
"strict": true,
}
}
Edit
I've posted the code on GitHub
Your need to use TypeScript v4.7 which is currently the TS-Next Version
Once you upgrade to typescript#next which can be done by executing the command ~/$ npm install -D typescript#next, you will need to make the changes below to your tsconfig.json file.
{
"compilerOptions": {
"lib": [
"ESNext" /* ESNext includes new Level-4 features that were
recently added to the ECMA-262 JS spec */
],
"module": "NodeNext",/* (1 of 2) TS v4.7 settings you need
to change */
"moduleResolution": "NodeNext", /* This is the one that will
specifically solve the error you're
getting. Without the internal changes
made by this, your project will not
resolve modules correctly. */
"esModuleInterop": true, /* This is properly configured. FYI you cannot
change this, it must be set to true. */
/*
THE REST OF THE SETTINGS DO NOT AFFECT THE MODULE TYPE OR HOW TSC
RESOLVES OTHER MODULES */
"target": "ES2021",
"rootDir": "./src",
"outDir": "./dist",
"forceConsistentCasingInFileNames": true,
"strict": true,
}
}
To Summarize
You must set the tsconfig.json keys module and moduleResolution as they are shown below.
`moduleResolution: "NodeNext"
module: "NodeNext"
You will need TypeScript v4.7
Personally I keep a global property, so below I show the command for the global install, but all you really need is to add it to your node_modules dir it as a dependency for your current project.
~$ sudo npm i -g typescript#next // Global Install
~$ npm i -D typescript#next // Add as a Project Dependency
I can't help with ever IDE in existance, but if you use VSCode, use the following configuration so your project uses the ver v4.7.
Then you need to set the following configuration
"typescript.tsdk": "./node_modules/typescript/lib",
package.json
You also need to enable ESM in for Node.. To do this you need to add the following to your package.json
/** #file "package.json" */
{
"type": "module"
}
...OR YOU CAN use the dot MTS (.mts) file extension for all of your files. There are advantages to both, but discussing the advantages is beyond the scope of this answer.
That should be it. It sounds hard but its actually easy once you have done it before.
For another helpful source:
The answer at this LINK covers this same subject with a bit more detail. I really suggest you check it out.

migrating from D3.js v6 to v7 in typescript + node environment

I'm trying to make d3 v7 work in Typescript + Node environment. The previous code on d3 v6 was working fine with these configurations:
tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
}
}
and there was NO "type": "module" in package.json file.
What I've tried (step-by-step):
Updated d3 to v7 and restarted the app; Error:
/app/dist/controllers/d3-node.js:4
const d3_1 = require("d3");
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/d3/src/index.js from /app/dist/controllers/d3-node.js not supported.
Instead change the require of index.js in /app/dist/controllers/d3-node.js to a dynamic import() which is available in all CommonJS modules.
which was expected due to d3 v7 changes.
knowing that I need to make Typescript compiler generate import statements instead of require, I changed the tsconfig.json to:
{
"compilerOptions": {
"target": "ES6",
"module": "ES2020", /* <------------- */
"moduleResolution": "node",
"esModuleInterop": true,
}
}
which results in this error (as expected):
import app from '../app.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
To solve the previous error, I added "type": "module" to package.json, hoping this would (finally!) solve the issue.
Basically the change caused all import statements to break; so as an example, this import:
import app from '../app'
results in:
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/dist/app' imported from /app/dist/bin/www.js
After finding this #13422 issue on GitHub, I know that I should add .js to the end of import statements to make it work.
But the project has dozens of import statements and BTW, as discussed in the issue, it doesn't feels right to have all those .js extensions, even though we're writing Typescript.
Is there any other way to make it work without adding the .js to all the import statements?
Thanks

Publish Typescript classes as npm package

I have written a set of Typescript classes that I am publishing as an NPM module to our internal package server. My other projects are able to retrieve this dependency using NPM.
I'd like to publish the classes in such a way that I can import them by referencing the package itself, similar to some other packages that I am using like Angular.
import { SomeClass } from 'mylibrary'
This is my current setup:
I've written a ./mylibrary.d.ts file:
export * from './dist/foldera/folderb'
//etc
./package.json:
"name": "mylibrary",
"main": "mylibrary.d.ts",
"files": [
"mylibrary.d.ts",
"dist/"
],
"types": "mylibrary.d.ts",
//ommitted other settings
src/tsconfig.json:
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "../dist/",
"baseUrl": "src",
"sourceMap": true,
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2016",
"dom"
]
}
}
When I try to import the package using the desired syntax, MS VS Code says everything is ok, but running my project results in a rather cryptic error message:
Module build failed: Error: Debug Failure. False expression: Output generation failed
I'm feeling lost in the jargon of modules, namespaces and packages. Any help is greatly appreciated.
If you place an index.ts file in the root folder of your package (same level as package.json), so that it re-exports all the functionality from your src/ folder, than you can publish it without building to js, keeping in mind that it will only be used in TS projects.
The negative side of this approach is that, your package will be compiled with your project every time, and possible changes in TS environment may lead to inability to compile the whole project (I have problems compiling one of my packages on versions < 2.4.2, for example).
// index.ts
export { Class1 } from 'src/Class1'
export { Class2 } from 'src/Class2'

error TS2307: Cannot find module 'app'

I want to use typescript on node side. I have a very simple server. tsconfig.file inside my server folder is as follows:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "../dist/serverBuild",
"typeRoots": [
"../node_modules/#types"
]
},
"exclude": [
"../node_modules"
]
}
I have an app.ts file which has express related configuration in it and then I have server.ts file which is importing app module from app.ts and it has code to create and start the node server.
But I am getting following error:
TSError: тип Unable to compile TypeScript
server.ts (11,22): Cannot find module 'app'. (2307)
other modules that I am importing in my server.ts file like http module is not throwing any such error. What am I doing wrong here.
Here is how I am importing modules:
import * as http from "http";
import * as app from "app";
Thanks!
To load files from your project include the path. Use:
import * as app from './app';
By using from 'app' you are saying there is a module named app installed in your project. If that was the case you would need to install the typings for that module or create it yourself.

Resources