Import from index.ts returns undefined - javascript

I have this very simple file structure consisting of
src
index.ts
services
my-service.ts
In index.ts I have the following
export const a = 3
and in my-service.ts I do,
import { a } from '../index'
console.log(a);
this logs undefined.
When I do
setTimeout(() => console.log(a), 100);
it logs 3.
I am running the solution using
ts-node -r tsconfig-paths/register ./src/index.ts
my tsconfig looks like
{
"extends": "../../tsconfig.json",
// "include": ["src/**/*.ts"],
"compilerOptions": {
"outDir": "dist",
"allowJs": false,
"baseUrl": ".",
"rootDir": "src",
"composite": true
},
"references": [
{
"path": "../shared-tools"
}
],
"exclude": ["dist"],
"include": ["src/**/*.ts"]
}
Which extends the following root tsconfig
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
// "baseUrl": ".",
"allowJs": false,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": [],
"typeRoots": [
"./node_modules/#types"
],
"paths": {
"#project/shared-tools": [
"../shared-tools/src"
]
}
}
}

That symptom suggests you have a cycle of dependencies between index.ts and my-services.ts — in addition to the dependency you've described on a in index.ts, index.ts relies on something supplied (directly or indirectly) by my-services.ts. This is called a cyclic dependency. A depends on B which depends on A.
With JavaScript's now-standard ESM static module syntax, cycles are possible but normally they can be resolved without your having to worry about them. In cases where they can't, instead of getting undefined, you get an error (if you use const as you have).
But you're telling TypeScript to rewrite your module syntax into Node's original CommonJS-inspired syntax (module.exports and require() calls). With CJS, cycles can result in your seeing undefined for things that later have a value assigned when all the module code has been run.
Ideally, the solution is to remove the cyclic dependency. If index.ts has to depend on my-services.ts, perhaps move whatever my-services.ts imports from index.ts to something else they both import from. That way, A and B both depend on C rather than on each other.
If for some reason you can't do that, you might try telling TypeScript to output JavaScript module syntax instead ("module": "ES2015" or "module": "ESNext"). Since that does static resolution (if you don't use dynamic import()), it may be able to handle the cycles for you. Node now has robust support for ESM.
Or you may not need to handle it. Even in CJS, cyclic dependencies are really only a problem if you use the dependencies in the initial module code rather than in code that runs later in response to an event (as you saw by wrapping your code using a in a setTimeout). So if the resources involved in the cycles are only needed to (for instance) respond to a network request, you may not need to worry about it at all. There's a bit more about cycles in CJS modules in Node here.

Related

Export TS to support ES6, CommonJS, and HTML <script> tag

I'm coding up a TS library that exports an object like this:
export const MyLibName: SDKExport = { ... }
This library will be distributed on npm and I want others to be able to import it regardless of their setup, i.e. it should be able to be used in the following ways:
// ES6
import { MyLibName } from './myLib';
// CommonJS
const { MyLibName } = require('./myLib');
// Browser
<script src="https://cdn.jsdelivr.net/npm/myLib/dist/dist.min.js"></script>
<script console.log(MyLibName) </script>
The browser part is proving to be the most tricky since it requires "polluting" the global namespace with the MyLibName object. I can do that, but it's unclear to me how to do it in a way that doesn't pollute the global namespace when importing the library explicitly using CommonJS/ES6.
Here's my tsconfig.json:
{
"compilerOptions": {
"lib": ["ES2019"],
"module": "es6",
"target": "es5",
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
"outDir": "build",
"strict": true,
"noImplicitAny": true,
},
"include": ["./**/*.ts"],
"exclude": ["node_modules", ".vscode", "test"]
}
It's clear that the script you're importing in the browser must be a different thing than your CommonJS/ES6 import if you want one to pollute the global namespace but not the other. Just create a file like this one and use your bundler on it to create a "browser build" of your library:
import { MyLibName } from './myLib';
window.MyLibName = MyLibName;
(You seem to have a bundler/minifier in your project already so I won't go into detail on how to set up those.)
I tried using the approach suggested by Vojtěch Strnad with browserify, but encountered issues that were hard to debug due to all the extra code that browserify adds to the exported files. I'm accepting that answer as it's the correct one for complex codebases.
For my own project, which only includes a single file, I found that it worked well to export the TS code using the "module": "ES6" TS compiler options and run sed -i 's/\export //g' dist/index.js to remove the export keyword. That way, the exported file simply has const MyLibName = { ... } and thereby adds the object to the global namespace.

Disable ts errors in js project

So I have a cra app and I have set up a .eslintrc.json config with the help of eslint --init. Here's how it looks:
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"airbnb",
"prettier"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"react/jsx-filename-extension": "off"
}
}
I also have a jsconfig.json file. From what I understood, this is derived from the tsconfig.json config. Here's how mine looks:
{
"compilerOptions": {
"baseUrl": "./<path/to/src>",
"checkJs": true,
"jsx": "react"
},
"exclude": ["node_modules", "build"]
}
From what I've read, if you set checkJs to true you get some features like auto-imports and from what I've tested, this is true and I like it. But what I don't like is having ts errors in my js files and getting recommended to add ts-ignore to the top of the file or to a certain line. What I can do is set the checkJs flag to false and those will go away, but so would some features like auto-import.
Can I keep the checkJs flag to true and still disable all ts errors in my js project? Is there a eslint rule for that? From what I saw, I would need to install a bunch of ts eslint packages and alter my eslint config, but this seems a bit off. Can this be done in a somewhat elegant way?
I have worked on a monorepo project that uses TypeScript for type declaration files generation only (.d.ts files) based on JSDoc/TSDoc annotations. So, no real TypeScript code is used, only special comments.
This is the merged configuration for one of the packages:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"lib": ["ESNext", "DOM"],
"allowJs": true,
"checkJs": true,
"noEmitOnError": true,
"strict": true,
"noImplicitAny": false,
"strictNullChecks": false,
"esModuleInterop": false,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"removeComments": false,
"types": ["node", "mocha", "chai", "webpack", "tapable", "react"],
"allowSyntheticDefaultImports": true,
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
"declarationDir": "./types"
}
}
We get full type checking and no errors that can't be solved by pure JavaScript (our TypeScript version is ^4.2.4).
However, keep in mind that when you work with a type checker, generally you'll be forced to write your code in a stricter way (it is a pain, but it is worth it).
If you don't want to deal with TypeScript, because it may be hard to setup and maybe it is an overkill if you won't use all of its features, you can try JSDoc (in my opinion, it should have been the standard for JavaScript type annotations, but the reality is far from that).
Also consider that it is possible that you will have to provide type annotations somehow (the only way to use TypeScript as a type-checking engine without actually writing TypeScript is by using JSDoc or TSDoc which is a variant of the former specifically designed for TypeScript).
Here is a great article (there are many more, I recommend you to learn more about JavaScript type checking) (see this that specifically writes about JSDoc + TypeScript).
In our project, we do things like this:
/** #type {Array<number>} */
const myNumbersArray = [];
myNumbersArray.push(0); // OK
myNumbersArray.push('0'); // ERROR
Or
/** #module core/modules/string-utils/split-by-words */
import {parse} from './parse.js';
/**
* #description Splits passed string by words.
* #param {string} str - Input string.
* #returns {Array<string>} Results array.
*/
function splitByWords(str) {
return parse(str).map((item) => item.content);
}
export {splitByWords};
Finally, there are many JSDoc ESLint libraries that you can use.

Module '"domhandler"' has no exported member 'DomElement'. Did you mean to use 'import DomElement from "domhandler"' instead?

I trying to build a react-typescript project. ii didn't install dom utils and HTML parser libraries. but when I tried to run yarn build, the following error is coming
~~~~~~~~~~
../node_modules/#types/htmlparser2/index.d.ts:17:10 - error TS2614: Module '"domhandler"' has no exported member 'DomElement'. Did you mean to use 'import DomElement from "domhandler"' instead?
17 export { DomElement, DomHandlerOptions, DomHandler, Element, Node } from 'domhandler';
~~~~~~~~~~
../node_modules/#types/react-html-parser/index.d.ts:9:10 - error TS2305: Module '"htmlparser2"' has no exported member 'DomElement'.
9 import { DomElement } from "htmlparser2";
I tried some of follwoing steps,
added "skipLibCheck": true in tsconfig.json file
https://github.com/apostrophecms/sanitize-html/issues/333 i tried some steps whatever they said, still no use.
tsconfig.json file
{
/* https://www.typescriptlang.org/docs/handbook/react-&-webpack.html */
/* https://www.typescriptlang.org/docs/handbook/compiler-options.html */
"compilerOptions": {
"target": "es5",
"module": "commonjs" /* webpack supports imports syntax */,
"jsx": "react",
"lib": ["es5", "dom", "es2015"],
"strict": true,
"moduleResolution": "node" /* load modules like node */,
"esModuleInterop": true /* to treat commonJS 'module.exports' as 'default export' */,
"importHelpers": true,
"skipLibCheck": true,
}
}
This might be a duplicate question. But I don't know how to solve this issue, can someone help me for solving this??
If you have these errors just check your package.json.
Possibly you installed unwanted type definitions because htmlparser2 package has all necessary type definitions from the box.
If so then delete it and use #Kasunaz's answer.
I fixed this issue by configuring Typescript (editing tsconfig.json) to "Skip type checking of declaration files." (docs):
{
"compilerOptions": {
"skipLibCheck": true,
...
}
}

TypeScript -> JavaScript for browser: exports/require statements

I want to switch from JavaScript to TypeScript for our web app's scripts. However, when generating the JavaScript it always puts the following lines on top of the script:
"use strict";
exports.__esModule = true;
var $ = require("jquery");
I receive browser errors for this. How to prevent TypeScript from doing so?
I read TypeScript: Avoid require statements in compiled JavaScript but it can't be the answer to switch to "any", this forfeits the whole TypeScript idea.
I also read Typescript importing exported class emits require(...) which produces browser errors but this still generates the <reference... stuff into the JS file, which is also not what I want.
How to create "clean" JS files?
My tsconfig.json looks like this:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5"
},
"compileOnSave": true
}
My gulp call is:
var ts = require('gulp-typescript');
...
var tsProject = ts.createProject("tsconfig.json");
...
gulp.task("ts", function () {
return gulp.src(tsInputFiles)
.pipe(tsProject())
.js
.pipe(gulp.dest("wwwroot/js"));
});
Just drop the module loading in the beginning of your typescript file, if you do not want to load any modules?
file1.ts:
$(function () {
});
If you try to compile this, you'll get an error:
file1.ts(2,1): error TS2581: Cannot find name '$'. Do you need to
install type definitions for jQuery? Try `npm i #types/jquery` and
then add `jquery` to the types field in your tsconfig.
Run npm as told above (npm init first, if npm has not been initialized in the directory).
Then add typeRoots and types to tsconfig:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5",
"typeRoots": [ "node_modules/types" ],
"types": [ "jquery" ]
},
"compileOnSave": true
}
After that, the compiling works, and you still have the strong typing in place (jquery types applied in compilation).
Browsers don't support JavaScript modules, so you'll need to tell the TypeScript compiler that you're trying to compile code that will be run on a browser. I don't know what the rest of your project looks like, but in your configuration, try adding "module": "es2015" or "module": "system" to your configuration, while also adding an outFile defining where you want to file to be generated :
in your tsconfig file :
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5",
"module": "es2015",
"outFile": "script.js",
},
"compileOnSave": true
}
I wanted to use TypeScript files injected directly into a HTML file via gulp task (compiled into es5).
At first adding type="module" to <script> helped. But then more problems appeared, so finally I've end up using this notation instead of import:
/// <reference types="#types/jquery" />
/// <reference path="my_custom_file.ts" />
/// <reference path="my_externalTypes.d.ts" />
and namespace in each of the file to separate them from each other.
That should work for a simple, small projects.

ES6 Style Import in Typescript

I would like to import an external module from node_modules in node using typescript using the import syntax. I've added the type definitions to my project so I can call install() without typescript returning errors (I know I can do an any cast require but I would obviously like to keep everything typed if possible).
/// <reference path="typings/source-map-support/source-map-support.d.ts" />
import * as sourceMapSupport from 'source-map-support';
sourceMapSupport.install();
However when my code is output it returns the following:
/// <reference path="../typings/source-map-support/source-map-support.d.ts" />
//# sourceMappingURL=server.js.map
Could someone explain to me why it doesn't output a require in the resulting js file? Also I'm compiling to ES6 as I need a access to the async & await keywords - my tsconfig.json is as follows.
{
"compilerOptions": {
"target": "es6",
"sourceMap": true,
"outFile": "build/server.js"
},
"exclude": [
"node_modules",
"typings"
]
}
Could someone explain to me why it doesn't output a require in the resulting js file
Because you are using outFile in tsconfig "outFile": "build/server.js". Don't use outFile if you are using a module format. That is a job a bundler e.g. webpack.
{
"compilerOptions": {
"target": "es6",
"sourceMap": true,
"module": "commonjs"
},
"exclude": [
"node_modules",
"typings"
]
}
PS
Note : There are edge cases here e.g. systemjs / amd support outFile. But that's not most likely not relevant here.

Categories

Resources