Nodejs: After Import of a module, code in the file is not getting executed - node.js

I am working on few files in nodejs app with typescript
Concerning files are as follows:
some-module.ts
console.log('inside some-module');
export default class Foo {
/*...*/
}
/*execute some imp code*/
Injector.provideClass('Foo', Foo);
console.log('done with some-module');
app.ts
console.log('inside app.ts');
import Foo from './some-module'; // this does not execute console.log! Since Foo class is only referred and not actually used in app.ts file.
class App {
static inject = ['Foo'];
private foo: Foo;
constructor(foo: Foo) {
this.foo = foo;
}
/*...*/
}
Injector.injectClass(App); // This throws error as Foo is not provided in injector.
console.log('Done with app.ts');
Output (console):
$ inside app.ts
$ Done with app.ts
Already tried:
I have tried with const Foo = require('./some-module');, this works but its not the best way to do it in typescript. Since it does not recognize the types in it.
Also -> If if put imported Foo in console.log right after import statement then also it works. Since Foo is now getting used in imported module file.
import Foo from './some-module';
console.log(Foo); // <- After adding this simple log, it works!
Also -> If you do only import import './some-module';
But this way you dont have access to its types.
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": false,
"outDir": "dist",
"baseUrl": ".",
"lib": ["es2015"],
"paths": {
"*": [
"node_modules/*"
]
}
},
"include": [
"src/**/*"
]
}

Related

How do I import from a relative path like "/utils" within my "src" folder in Jest + RTL Tests

I am creating tests with Jest and React Testing Library and I am having difficulties with imports to the util folder that exists within the src folder. I am using TypeScript for this which seems to create its own interesting challenges.
My jest.config.ts file looks like the following:
module.exports = {
'preset': 'ts-jest',
'testEnvironment': 'node',
'testPathIgnorePatterns': [
'<rootDir>/dist'
],
'moduleNameMapper': { 'src/(.*)': '<rootDir>/src/$1' }
};
The test looks like the following:
import React from 'react';
import { render, screen } from '#testing-library/react';
import { Tag } from '/components';
describe('Tabs Component', () => {
it('should render a tab component correctly', () => {
render(<Tag id="test" />);
expect(screen.getAllByTestId('test')).toHaveBeenCalled();
});
});
When I run this I am getting import issues:
Cannot find module '/components' from 'src/components/Tag/Tag.spec.tsx'
Additionally my tsconfig looks like the following:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"/*": [
"*"
]
},
"outDir": "./tested/esm",
"module": "esnext",
"target": "es5",
"lib": [
"es6",
"dom",
"es2016",
"es2017"
],
"jsx": "react",
"declaration": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"allowSyntheticDefaultImports": true
},
"include": [
"src/**/*.spec.{ts,tsx}",
"./jest.config.ts"
],
"exclude": [
"node_modules",
"dist",
"./src/**/*.stories.tsx",
"./*.js"
]
}
I have tried a few of the tricks from Stack Overflow like:
Jest gives `Cannot find module` when importing components with absolute paths
None of these seemed to fix the issue.
I think the problem here is how the import is done. As the jest.config.ts has already specified the moduleNameMapper as { 'src/(.*)': '<rootDir>/src/$1' }, it means if Tag component is imported as below,
import { Tag } from "src/components";
Jest will look into the <rootDir>/src/components/index.ts to import the Tag component.
If you want to import modules in this way,
import { Tag } from "components"; // not "/components"
It's better to replace moduleNameMapper config with moduleDirectories:
module.exports = {
'preset': 'ts-jest',
'testEnvironment': 'node',
'testPathIgnorePatterns': [
'<rootDir>/dist'
],
'moduleDirectories': ['node_modules', 'src']
};
This tells Jest when it sees an import from "components", it will look into both node_modules and src and see if there is the file components/index.ts or components.ts.
PS: tsconfig.json is only used by the editor. Therefore, Jest won't take it into consideration. jest.config.ts is the config file that matters to Jest.

Importing a CommonJS module in a Typescript project exporting ESModules

I am making a NodeJs library in Typescript and I plan to make it support Node#14+. So, I decided to base this project as a ESModule instead of CommonJs. Now there are some libraries required that are CommonJs and I am installing their #types. The imports of my typescript file looks like this:
import { Command } from 'commander';
import inquirer from 'inquirer';
import { resolve } from 'path';
import { render } from 'mustache';
import { copySync, readdirSync, readFile, readFileSync, writeFile } from 'fs-extra';
and the tsconfig.json looks like this:
{
"include": ["src/**/*"],
"compilerOptions": {
// type checking
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
// module
"module": "ES2020",
"moduleResolution": "node",
"rootDir": "src",
"resolveJsonModule": true,
// emit
"outDir": "dist",
// "importHelpers": true,
// imterop constraints
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
// language and environment
"lib": ["ES2020"],
"target": "ES2020",
// completeness
"skipLibCheck": true
}
}
As you can see, I am targeting ES2020 as that is fully supported by Node#14+ and I based my config on the incredible answer here https://stackoverflow.com/a/61305579/5863472. Also, I am setting "type": "module" in my package.json.
This builds with no problem but doesn't run. The error thrown by Node is
import { copySync, readdirSync, readFile, readFileSync, writeFile } from 'fs-extra';
^^^^^^^^
SyntaxError: Named export 'readFile' not
found. The requested module 'fs-extra' is a CommonJS module, which may
not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'fs-extra';
const { copySync, readdirSync, readFile, readFileSync, writeFile } = pkg;
The same codebase compiles well using "module": "ES2020" in my tsconfig.json and removing the type key in my package.json.
The way I have found to work around this is by following the error message. This works fine:
import { Command } from 'commander';
import inquirer from 'inquirer';
import { resolve } from 'path';
import mustache from 'mustache';
import fs from 'fs-extra';
const { copySync, readdirSync, readFile, readFileSync, writeFile } = fs;
const { render } = mustache;
But this feels like such a pain and seems like something that could be tooled. Am I missing and compilation option or something that can automatically convert import { foo } from 'bar' to import bar from 'bar'; const { foo } = bar.
P.S. I really don't want to use Babel...

TypeDI #Inject() doesn't work, but Container.get() does

I'm facing weird issue,
Container.get(MyServiceName); returns demanded service, yet class property decorated with #Inject() is undefined.
I've tried decorate class with #Service()
I do import reflect-metada at entry point of my application
import 'reflect-metadata';
I'm wondering if it may be related to fact, that that I'm using typedi not directly from my node_modules but from my dependency?
Application doesn't use Express framework neither routing controllers
My tsconfig.json:
"compilerOptions": {
"declaration": true,
"pretty": true,
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": false,
"moduleResolution": "node",
"noUnusedParameters": true,
"strictPropertyInitialization": false,
"module": "commonjs",
"lib": ["dom", "es2018"],
"importHelpers": true,
"outDir": "./dist",
"strict": true,
"typeRoots": ["node_modules/#types"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "./dist/**/*"]
}
Service that I want to be injected
import MyServiceName from '../services/MyServiceName';
import { Container, Inject, Service } from 'my_dependency/lib';
export default class SomeClass{
#Inject()
private readonly someService: MyServiceName;
public async someMethod(): SomeResponseType {
const thatWorks = Container.get<MyServiceName>(MyServiceName);
console.log(this.someService); // undefined
console.log(thatWorks); // proper class
}
}
Service I want to inject
#Service()
export default class MyServiceName {
public async someMethod(): SomeReturnType {
return someReturnedData;
}
}
I would like to inject my dependencies though #Inject() decorator
You need to create your instance of SomeClass using Container#get, or the container won't be able to inject the property.
This worked for me:
// a.ts
import { Service } from "typedi";
#Service()
export class Foo {
foo() {
return 10;
}
}
// b.ts
import { Foo } from "./a";
import { Inject } from "typedi";
export class Bar {
#Inject() private readonly service: Foo
foo() {
console.log(this.service);
}
}
// test.ts
import "reflect-metadata";
import { Bar } from "./b";
import { Container } from "typedi";
const noFoo = new Bar();
noFoo.foo(); // yields undefined
const withFoo = Container.get<Bar>(Bar);
withFoo.foo(); // yields Foo

*export ... from* returns undefined in node with typescript

I'm aggregating files in an index.ts. When I try to import from the index.ts the object returns as undefined. When I try to import directly from the file, it works fine. I'm not sure what I'm doing wrong -- it's my impression that export { ... } from '...' should work fine in Node with TypeScript (which is the case for this app).
fileThatUsesTheExportedVariables.ts
import { dates } from '../consts' // Undefined
import { dates } from '../consts/index' // Undefined
import { dates } from '../consts/dates' // Works
consts/index.ts
export { dates } from './dates'
// ...
export { someNamedExport } from './someFile'
consts/dates.ts
const months = {
// some months stuff
}
export const dates = {
months,
// ...
someOtherChildObject
}
I presume I'm doing something silly. VS Code can connect the dots which makes it all the more frustrating.
Thanks in advance for any and all help
UPDATE: #casieber pointed out it might be helpful to know more about the TS compilation process (uses tsc):
Versions:
tsc 2.9.1
typescript 6.1.0
tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": ["es2018", "dom"],
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"app/**/*"
]
}

TypeScript ES dynamic `import()`

While using the new TypeScript feature, so called ES Dynamic Imports, I am not able to run the code of my isomorphic app on the server side using ts-node.
It seems like the error does not occur when using the webpack module loader which transpiles the code in it's own way and running resulting files in a browser.
The error which I've got:
case 0: return [4 /*yield*/, import("./component/main")];
^^^^^^
SyntaxError: Unexpected token import
Usually TypeScript transpiles the import expression to something like that: Promise.resolve(require("./component/main")), but I can't see it there.
How to fix that? Does it have something common with ts-node? Or there is a "polyfill" for node.js?
My tsconfig.json file:
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"allowJs": false,
"experimentalDecorators": true,
"importHelpers": true,
"inlineSourceMap": false,
"inlineSources": false,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
],
"listFiles": false,
"module": "commonjs",
"noEmitOnError": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"preserveConstEnums": false,
"pretty": false,
"removeComments": false,
"strict": true,
"target": "es5"
}
}
the code:
import * as m from "mithril";
import LayoutComponent from "./component/layout";
const render = (
layout: m.ComponentTypes<any, any>,
) => ({ tag, attrs }: m.Vnode<any, any>) => m(layout, attrs, m(tag as any, attrs));
export default {
"/:path...": {
onmatch: async (args, path) => (await import("./component/main")).default,
render: render(LayoutComponent),
},
} as m.RouteDefs;
This is a bug in the Typescript Compiler which will be fixed in 2.5.
Exporting a default object with a function that imports a file will not compile the import statement into a require statement in Typescript 2.4.x.
For example, while this:
export const sudo = { run() { return import('./test3'); } }
Will compile to this:
exports.sudo = { run: function () { return Promise.resolve().then(function () { return require('./test3'); }); } };
This:
export default { run() { return import('./test3'); } }
Compiles into this:
exports.default = { run: function () { return import('./test3'); } };
Which is obviously wrong. A temporary solution would be this:
export const sudo = { run() { return import('./test3'); } }
export default sudo;
Which compiles (correctly) into this:
exports.sudo = { run: function () { return Promise.resolve().then(function () { return require('./test3'); }); } };
exports.default = exports.sudo;

Resources