How to use TypeScript import statement instead of <reference path...> in a Web application (ASP.NET Core)? - node.js

Context
I have (had) a working version typescript Hello World in my Web application (ASP.NET Core)
Using typscript compiler via NuGet package "Microsoft.TypeScript.MSBuild" Version="4.4.2" and tsconfig.json. (see below)
I've wanted to use a 3rd party lib, and successfully added "#types/lodash": "^4.14.175" via packages.json (see below)
I've added /// <reference path="../node_modules/#types/lodash/index.d.ts"/> (see below)
All works, but the line /// <reference path="..." is underlined green and ESLint says
Do not use triple slash reference for index.d.ts, use import instead.
OK, I am going to use export/import later anyway, so I've edited the triple slash reference line to be a comment, and added the line import * as _ from "lodash" which compiles fine, but when running in chrome causes runtime error:
Cannot use import statement outside a module
so I changed my <script tag to the following: <script type="module" src="~/js/app.js"></script>
However this causes the following chrome runtime error:
Failed to resolve module specifier "lodash". Relative references must start with either "/", "./", or "../".
Question
Now I am stuck, and with my very limited knowledge somehow I guess some step/transformation is missing, but what? I've tried to include some path in my .ts file's import statement (causing compile errors). Compile time I would like to use the working import referring to the #typings, but runtime the lodash.js is coming from cdn, the two nothing to do with each other...
app.ts
// commented out / <reference path="../node_modules/#types/lodash/index.d.ts"/>
import * as _ from "lodash"
console.log(_.camelCase("Hello"));
emitted app.js
// commented out / <reference path="../node_modules/#types/lodash/index.d.ts"/>
import * as _ from "lodash";
console.log(_.camelCase("Hello"));
//# sourceMappingURL=app.js.map
index.html
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
<script type="module" src="~/js/app.js"></script>
tsconfig.json
{
"compileOnSave": true,
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es6",
"module": "ES6",
"outDir": "wwwroot/js"
},
"exclude": [
"node_modules",
"wwwroot"
]
}
packages.json
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"devDependencies": {
"#types/lodash": "^4.14.175"
}
}

Try to modify the tsconfig.json
// tsconfig.json
{
...
#types: ["node_modules/"] // or typings
}
Or use ES5 require
const _ = require("lodash");
Found the following possible solutions
The required files need to be copied over to wwwroot folder, where they can be accessed when the application runs.
For this you'd need either use the bundler to bundle the files together (should be in default ASP.NET Core project template) or use task runners such as Gulp or Grunt to run tasks on build/publishing, which does that for you. See ASP.NET Core Docs on Gulp examples.
Original answer: https://stackoverflow.com/a/43513137/13747848
Note: Please give credit to original respondent!
Edit
For the error
Uncaught TypeError: Failed to resolve module specifier "lodash". Relative references must start with either "/", "./", or "../".
As of 2021, please consider the following statement by Márton Salomváry (Jan 2018):
Unfortunately even most libraries authored or published in ES6 module format will not work because they target transpilers and rely on the Node.js ecosystem. Why is that a problem? Using bare module paths like import _ from 'lodash' is currently invalid, browsers don’t know what to do with them.
And also the statement by Jake Archibald (May 2017):
"Bare" import specifiers aren't currently supported.
Valid module specifiers must match one of the following:
A full non-relative URL.
Starts with /.
Starts with ./.
Starts with ../.
And javascript.info:
In the browser, import must get either a relative or absolute URL. Modules without any path are called “bare” modules. Such modules are not allowed in import.
Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.
Bundlers facilitate the use of "Bare Imports" which is not supported by the browser yet. Unless you bundle your code, I recommend using the solution proposed by #Asler. Besides, a lot of work is currently being done to study the implementation of "Bare Imports" in the browser, please follow this link if you want to monitor the overall progress.
Original answer: https://stackoverflow.com/a/66484496/13747848
Note: Please give credit to original respondent!
If you don't wish to use any bundling tools, you will need to provide a path to the lodash folder within node_modules, relative to the JavaScript file that you have the import statement in.
If you do not wish to use a bundler, it would also be worthwhile importing from the specific file, the function you need. For example:
import _each from '../node_modules/lodash/each'
Original answer: https://stackoverflow.com/a/52558858/13747848
Note: Please give credit to original respondent!

Related

Persistent undefined error in typescript import export

There's already a LOT of questions about typescript in multiple files.. for instance, this one,
Typescript import/export
Interesting question and answer, I simplified and tested it, see below.. but whatever I try, I still get
Uncaught TypeError: Cannot read properties of undefined (reading 'A')
.. as does any other example of import/export in TypeScript I found online. Whatever I do, whatever object I try export (class, function, const) with or without using a module: I get the same error.
Maybe there is something wrong in my NPM/TSC/React configuration ? Should I change e.g. tsconfig.js when i want to use more than one typescript file in a project ? I'm lost, what do I miss ?
tsconfig.json
{ // TypeScript configuration file: provides options to the TypeScript
// compiler (tsc) and makes VSCode recognize this folder as a TS project,
// enabling the VSCode build tasks "tsc: build" and "tsc: watch".
"compilerOptions": {
"target": "es5", // Compatible with older browsers
"module": "umd", // Compatible with both Node.js and browser
"moduleResolution": "node", // Tell tsc to look in node_modules for modules
"sourceMap": true, // Creates *.js.map files
"jsx": "react", // Causes inline XML (JSX code) to be expanded
"strict": true, // Strict types, eg. prohibits `var x=0; x=null`
"alwaysStrict": true // Enable JavaScript's "use strict" mode
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
first.tsx
const A ={
val: 'A'
}
export { A }
app.tsx
import { A } from "./first";
// ... other code
function reportPerson()
{
console.log(A);
}
.. Both files translate to .js with TSC, but A is reported by the Google Chrome console as undefined,
Both tsx files are in the same directory, TSC converts them both to JS without any issue.
What's going on ?
Thanks everyone for the advice (I didn't solve the above minimal example either..)
In order to properly link my stuff together, I've now put Parcel 2 to work,
https://www.npmjs.com/package/parcel
npm i parcel
This is basically a bundler, that allows separate ts files to be concatenated after they are compiled to Javascript and it will put everything in a \dist directory,
parcel build src/index.html
Based on a small react example, I put my first "modulized" little app in TypeScript to work. Then, with the help of expert advise, I proceeded with twgl.js, which is a great toolkit for Webgl2.
npm install twgl.js
This javascript library even has sub-modules.. and everything links fine now, I can access (all of?) twgl with
import * as twgl from "./twgl-full.js";

How to specify the distributable directory visible for both NPM and TypeScript? (Multiple files case)

The library written in TypeScript includes three main files for distribution:
NodeJS.js - for, obviously, Node.js runtime.
BroswerJS.js - for, obviously, browser runtime.
index.js - common functionality for both browser and Node.js
There no "main" file in this library so I has not specified this property in package.json.
Planning usage:
import { isUndefined, isNull } from "package-name;
import { delegateClickEventHandling } from "package-name/BrowserJS;
import { NodeJS_Timer } from "package-name/NodeJS;
Currently, the TypeScript with below config compiles files below Source directory to Distributable directory:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"removeComments": true,
"outDir": "Distributable/",
"declaration": true
},
"include": [ "Source/**/*" ]
}
If to publish the library such as, TypeScript even will not see it:
import { isUndefined } from "package-name";
TS2307: Cannot find module 'package-name' or its corresponding type declarations.
Because as default TypeScript expecting that .d.ts files will be in root directory of the library. But the distributables are in Distributable directory!
And of course, isUndefined will not be found. I know about "main" property in package.json, but it is for one file case, but what about directory?
I know that multiple distributable files exporting is the supported scenario. For example the mysql2 exporting promise.ts besides index.js:
import MySQL from "mysql2";
import MySQL_Promise from "mysql2/promise";
Update
The NPM part solved - modern solution is exports filed in package.json:
"exports": {
".": "./Distributable/index.js",
"./NodeJS": "./Distributable/NodeJS.js",
"./BrowserJS": "./Distributable/BrowserJS.js"
},
But distribution files are still invisible for TypeScript.
TS2307: Cannot find module 'package-name' or its corresponding type declarations.
I learned about "types" field of package.json. Unfortunately, it could be only a string. It means currently it's impossible to specify multiple files. The issue about making in to array has been declined.
But how to make visible all of "./Distributable/index.js", "./Distributable/NodeJS.js", "./Distributable/BrowserJS.js" for TypeScript?
Please don't suggest me again to make all imports to single entry point. In this question we considering the multiple entry points case.
I am not entirely sure what you are trying to achive, in TS generally when you have single project with 1 configuration file, and you emit multiple files from it, you would not use package name within the same project, use path instead './someFileName'.
If you have multiple projects (tsconfig files) to manage different directories - sort of monorepo thing going on.
Your best options is project references: https://www.typescriptlang.org/docs/handbook/project-references.html
Or if you are doing something else then this may help altho I'd do this as last resort :-)
https://www.typescriptlang.org/tsconfig#paths

Typescript adds plain export statement

I am building some typescript/react console app for Node.js.
If there is any import module in the source code it adds the export {}; into the output.
How I can get rid of the stuff please?
I use
typescript 4.1.2
ts-node
tsconfig:
{
"compilerOptions": {
"module": "ESNext",
"jsx": "react",
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
]
}
Source code:
import { useState, useEffect } from "react";
console.log("aaa");
Output:
console.log("aaa");
export {}; // <------ the problem
Fun fact: when I remove the import, the export disappear.
With the introduction of modules in ECMAScript 2015, ECMAScript has been split into two slightly incompatible languages: Scripts and Modules. However, there is no way to explicitly mark a resource as either a Script or a Module in-band. The only way to do that is out-of-band, e.g. via HTTP Content-Types or a command line flag that explicitly tells the ECMAScript engine to interpret the file as a Module.
So, in order to make it clear to the ECMAScript execution engine, that this is, in fact, a Module not a Script, the only way is to make sure that the resource can only legally interpreted as a Module and not a Script.
An empty export statement serves that purpose, since it is illegal in Scripts but has no side-effects.
Long story short: you cannot remove the export statement, because that will make the file ambiguous: it is impossible to tell from the source code alone whether the file is a Script or a Module. And for reasons of backwards-compatibility, most ECMAScript engines interpret ambiguous resources as Scripts. (A more strict engine might reject the file altogether, which is also not what you want.)
So, if you remove the export statement, your file will no longer be interpreted as a Module. However, the TypeScript source file is a Module (because it contains an import statement), therefore the compiler must emit the export statement in order to ensure that the compiled file is also a Module.

Use non-relative imports using TypeScript's baseurl property without WebPack

I am writing a node web application using TypeScript and Express.
I managed to get everything working, however the issue I have run into is that my imports don't seem to respect the baseUrl option of my tsconfig.json.
Here is how my tsconfig.json looks like:
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist",
"baseUrl": "./src"
},
"exclude": [
"node_modules"
]
}
As an example, let's say I have the following files:
- dist/
- a/
- car.js
- b/
- hat.js
In car.js I can easily require hat.js by doing:
import hat from '../../b/hat'; // relative version
This works as expected.
However, I also want to be able to do the following:
import hat from 'b/hat'; // absolute version
This does not generate any issues during compilation or shows any IDE errors, as the tsconfig.json specifies the baseUrl as ./src. Thus the above is perfectly valid TypeScript code.
However, my expectation was that the code will compile down to the relative version:
const hat = require('../../b/hat');
Unfortunatly it compiled down to:
const hat = require('b/hat');
and thus predictably does not work.
Other users have solved this issue by using 3rd party tools such as: https://github.com/s-panferov/awesome-typescript-loader
https://decembersoft.com/posts/say-goodbye-to-relative-paths-in-typescript-imports/
But majority of these tools is designed to work with WebPack, which isn't really suitable for an node back-end application. This is because we are running a long-running server, and thus won't benefit from being bundled into a single file versus several different files (unlike front-end web development).
My question is, how can I compile my TypeScript files, without WebPack, so that absolute imports works correctly.
So the way to do it is to transform back the non-relative imports to relative imports after build so that commonjs will recognize them. And the way to easily do it is the following:
Step 1: Install the transform modules:
npm i -D typescript-transform-paths
npm i -D ttypescript
Step 2: Modify tsconfig.json
"compilerOptions: {
...
"plugins": [
{ "transform": "typescript-transform-paths" },
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
]
}
Step 3: Modify package.json
We will use ttypescript to build the project instead of tsc inorder to use transformers.
"build": "ttsc"
Step 4: Build and Start the project
npm run build
npm start
OP I haven't gotten this to work yet, but this article may provide some guidance: https://levelup.gitconnected.com/get-rid-of-relative-import-path-hell-in-your-typescript-project-9952adec2e84
TL;DR
yarn add link-module-alias
package.json
...
"_moduleAliases": {
"~": "dist"
}
...
My question is, how can I compile my TypeScript files, without WebPack, so that absolute imports works correctly.
My opinion
Don't. In fact don't do it even do it with webpack.
Reason
The node resolution algorithm is complicated enough : https://nodejs.org/api/modules.html#modules_all_together and doesn't need to be complicated further with "and oh, now we have baseurl".
If you still want to do it, here are your options.

Typescript2 path module resolution

tl;dr : module resolution does not apply ?
Hello,
I am playing around with Typescript2 module resolution feature.
I've noticed that it is now possible to specify "Paths", so that you can do the following :
Old way
import {a} from "../../../foo"
New way
import {a} from "services/foo"
To do so, you need to add some configs to your tsconfig.json
"compilerOptions": {
"baseUrl": ".",
"paths": {
"services/*": ["./application/core/services/*"],
}
}
Problem that I have, is that when compiled, the import actually doesn't change. My javascript output still contains that import from "services/foo", so that obviously crash at runtime on my node server.
I use gulp-typescript to compile my javascript files :
var tsProject = ts.createProject("tsconfig.json");
return tsProject.src()
.pipe(sourcemaps.init())
.pipe(tsProject()).js
.pipe(sourcemaps.write("../api"))
.pipe(gulp.dest(function(file) {
return file.base;
}));
I am completely lost here and would love to use that module resolution, so that I can move away from that ../../ hell of imports. Any help would be more than appreciated !
The problem here is that the JavaScript Engine knows nothing about your TypeScript config, what you specify in your tsconfig is only used "compile time", when you have compiled your TypeScript into JS you need to do the same job as the TS compiler did but to save the resolved path in the JS file.
Simply put, all JS files needs to be processed and the aliases replaced with "real" paths.
Tip: Use the npm tool tspath (https://www.npmjs.com/package/tspath), it requires 0 configuration, just run it in somewhere in your project and all JS files will be processed and ready to run!

Resources