NPM Package: Conflicts between ES libraries - node.js

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 ?

Related

Cannot import custom typescript package in node.js

So after a few hours of banging my head against the wall, I decided to ask stackoverflow.
I have a custom typescript project/package which I would like to use with zx. I used npm pack and installed the tar.gz globally, when I try to require or import my custom module I always get the error ERR_MODULE_NOT_FOUND and I can't seem to find the right typescript configuration to make this work. What has to be done to make a typescript module importable in a normal node.js script? This is my current tsconfig.json:
{
"compilerOptions": {
"lib": [ "es2015" ],
"target": "es5",
"esModuleInterop": true,
"moduleResolution": "node",
"downlevelIteration": true,
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"types": ["node", "jest"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
if this is to little information, here is the link to the repo https://github.com/mak1A4/sn-request
EDIT: I already have the NODE_PATH environment variable set in my .zshrc and also tried installing the tar.gz locally inside a normal node.js project, with the same result :-/
I changed my config based on this repo example-typescript-package didn't work either ... but after fixing the typo I made in the import statement it worked ^^

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.

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

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.

SyntaxError: Unexpected token import- Node.js

I am trying to code split my app with webpack but webpack is not creating the chunks for my dynamic imports. I found one source here:
https://davidea.st/articles/webpack-typescript-code-split-wont-work
which said I needed to change my module property in my tsconfig file from "commonjs" to "esnext" so the typescript compiler won't reduce my dynamic import statements to Promises, resulting in Webpack not knowing they are dynamic imports and thus not creating dynamic chunks. I followed this and during the compilation I can see the chunks now being created! Whoooo! However, the compiler bugs out, with the error in the title of my question, when trying to resolve the import statements in my express app as those ES imports are not being reduced to something node understands anymore. Does anyone know if this is possible to achieve? I want to use ES imports statements in node but without having the module property in my tsconfig file set to "commonjs". I really don't want to have to refactor all the imports statements to commonJS require statements.
tsconfig.json:
{
"compilerOptions": {
"target": "es6",
"module": "esnext",
"allowJs": true,
"sourceMap": false,
"inlineSourceMap": true,
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noEmitOnError": true,
"removeComments": false,
"forceConsistentCasingInFileNames": true,
"suppressImplicitAnyIndexErrors": false,
"jsx": "react",
"watch": false,
"moduleResolution": "node"
}}
node version: v8.11.3
I am using ts-node in my npm script to execute my server code:
"start": "webpack && ts-node -- ./src/service/index.ts --env=prod"
My .babelrc looks like the following:
{
"presets": [
"react",
"stage-3",
[
"env",
{
"modules": false
}
]
],
"plugins": [
"react-hot-loader/babel",
"syntax-dynamic-import"
]
}
By default, ts-node uses the compiler options in the tsconfig.json file in the working directory; you can specify a different tsconfig.json file with the --project option. The tsconfig.json file you use with ts-node must have module set to commonjs (or omitted, in which case ts-node defaults it to commonjs) in order for the on-the-fly compilation to generate modules that Node can understand. You may need to use separate tsconfig.json files for the server code you run with ts-node and the client code you package with Webpack.

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'

Resources