More issues with TypeScript modules - node.js

... yep. It's that time of year again. TypeScript is wonderful, except for the mess that is the module system. Presently, I'm struggling with a scenario where I'm dealing with two separate repositories / projects (what ever you wanna call it). I've got a library and a program using said library. I'm using Node's subpath exports feature in the package.json to indicate multiple submodules within the library. From the NodeJS (and the bundler's perspective) this is fine, but not in TypeScript's world. TypeScript is adamant that none of the modules exist.
Here's a quick summary of the library
/utils
package.json
tsconfig.json
build automatically generated build directory
iter.js
iterSync.js
...
lib autogenerated declarations files
iter.d.ts
iterSync.d.ts
...
src source files
iter.ts
iter_sync.ts
...
lib manually written definitions files
iter.d.ts
iterSync.d.ts
It's package.json has an exports field which defines a list of exports and their respective declaration file.
library/package.json
...
"exports": {
"./iter": "build/iter.js",
"./iterSync": "build/iterSync.js",
...
}
...
I've tried various alterations of the above, such as
Node16 module resolution strategy
Adding a types property to each export
Using the main and typings fields in library/package.json
Adding "lib" to the includes of library/tsconfig.json
Upgrading to TypeScript 4.6.4
Restarting the TS Language Server through the VSCode command menu
Basically all permutations of the above
How I use the library
I don't think it makes a difference, but I'm referencing the module through a pnpm link dependency statement for development, and a git://... url for production:
program/package.json
...
"dependencies": {
"jcake-utils": "link:../library", // dev
"jcake-utils": "github:J-Cake/jcake-utils", // prod
...
}
Regardless of what I do, TypeScript refuses to locate my submodules in the program
...
import Iter, * as iter from 'jcake-utils/iter';
import IterSync, * as iterSync from 'jcake-utils/iterSync';
...
$ pnpm exec tsc -v
Version 4.6.4
$ pnpm exec tsc -p program/tsconfig.json
src/build.ts:2:29 - error TS2307: Cannot find module 'jcake-utils/iter' or its corresponding type declarations.
... (A bajillion errors saying exactly the same thing, just across different files)
I alluded to this being a common issue (because from my experience it is) but I would greatly appreciate some very specific feedback as to how I can correct this issue and/or if I should potentially restructure either program or library, and how best to do so. I've been unable to find documentation that is helpful in my situation, as many of the suggestions given by the TypeScript community have not worked.
Thanks kindly

Related

Publish Typescript Interfaces with NPM

I'm working in a project that has multiple typescript projects. I'm trying to have a common package for all the interfaces, types, enums, etc.
I thought I could make it work creating a NPM package with Typescript and have an index.ts with this content:
When I'm working in the projects that depend on this package, everything seems fine, but when I want to start the development environment I'm getting this error:
I've got the suggestion of running ts-node with --skipIgnore flag, but I'm getting the same error:
Maybe I needed to compile the code and import the .js (doesn't make ANY sense, but at this point 🤷🏽‍♂️)... no luck.
Let me share both tsconfig.json:
The one from the "common" package:
The one from the project that depends on the common package:
Things suggested and tried:
Because your target is ES6, commonjs modules cannot use your package. The dependent project should change its module type to ES6 as well - #kellys
Changing project's module to ES6, brings me this error:
All right, let's add moduleResolution: "node" ... and I'm getting:
So I'm adding "type":"module" in package.json:

Flow+Webstorm "Cannot resolve module"

I have an existing project where we integrated Flow's type system into the react side. The project is electron-based so, by definition, a mono-repo. We ran in to all kinds of issues getting flow to recognize import statements.
node_modules imports would fail:
import _ from 'lodash'; // Flow: Cannot resolve module lodash
And more importantly, we wanted absolute pathing relative to our project:
import {MyComponent} from 'src/component/myComponent';
// Flow: Cannot resolve module src...
Finding a solution on this took a bit of digging, and the documentation is a little lacking in some areas, so I want to throw a compiled list of what actually worked out there.
TL;DR;
Get flow set up on webstorm so it is giving you module errors
Set up flow globally, and point webstorm's js settings to use Flow and point it at the global copy of flow-bin (not even the exe, just the dir)
add the following options to .flowconfig:
[options]
module.name_mapper='^src\/\(.*\)$' -> '<PROJECT_ROOT>/src/\1'
module.system=haste
Full version
A few basic steps have to be done to get flow to work in webstorm at all:
Install flow-bin globally
Several sources made the claim that flow-bin runs better globally
Install flow-bin globally
yarn global add flow-bin
or
npm i -g flow-bin
Double check that it gave you a current version of flow-bin, this
refused to work on 0.75.0 or earlier
Set up Webstorm's flow executable
On Webstorm: File > Settings > Languages & Frameworks > Javascript
Choose Flow as the JavaScript language version
Find where your package manager (yarn or npm) stores global files
On Windows+yarn this is C:/Users/[your username]/AppData/Local/Yarn/Cache/v1
On Windows+NPM this is C:/Users/fish/AppData/Roaming/npm/node_modules
That makes my flow path:
C:/Users/[your username]/AppData/Local/Yarn/Cache/v1/npm-flow-bin-[whatever]/
On Webstorm's JavaScript settings, Target the Flow package or executable to the global flow path we just found
Apply, ok
Setting up Flow's .flowconfig
.flowconfig setup side notes
I have a root git project with 2 parts, react, and electron. Flow does things based on where you put the .flowconfig file.
If it includes "all=true", remove that line and go add // #Flow to your files you want flow to check (otherwise it will start indexing all of node_modules
Reproducing my problem
Put .flowconfig in your react directory
Enjoy all the "Flow: Cannot import module" squiggly lines of doom
Solution to the module problem
This is my current .flowconfig
[ignore]
.*/build/.*
[include]
[libs]
[lints]
[options]
module.name_mapper='^src\/\(.*\)$' -> '<PROJECT_ROOT>/src/\1'
module.system=haste
[strict]
Why does this work?
Tells the name mapper to resolve modules that begin with src/ to the src/ directory so your absolute paths to your project's files work:
module.name_mapper='^src\/\(.*\)$' -> '<PROJECT_ROOT>/src/\1'
Tells flow to use the "haste" module system:
module.system=haste
The haste module system step is important because otherwise it doesn't know that by 'lodash' you mean './node_modules/lodash'. Telling it to use haste means it will properly find your import statements. More info on haste available here

Can I use bare imports for my local modules using native es6 modules (mjs)?

Background:
Assuming my folder structure is something like:
project/
-- package.json
-- index.mjs
-- lib/
-- config/
-- index.mjs
When I used require() natively in node I could reference a local module using a bare import like:
const x = require('config')
because I added the root of my library folder to the NODE_PATH environment variable. (assuming of course when I was using cjs/require the extension would have been .js)
The problem:
When I try to do that using native es6 modules (mjs) like:
import x from 'config'
I get the error:
Error [ERR_MODULE_RESOLUTION_LEGACY]: config not found by import in [SOME_ABS_PATH]/index.mjs. Legacy behavior in require() would have found it at [SOME_ABS_PATH]/lib/config/index.mjs
The question:
Anyone know how to solve this? Or what the future is for dealing with relative paths for local module resolution within the node native es6 module system?
Research so far:
Only resource I've found on this so far is from here http://2ality.com/2017/09/native-esm-node.html stating:
Path resolution works slightly differently: ESM does not support NODE_PATH and require.extensions. And its specifiers always being URLs also leads to a few minor differences.
And on the error message below ERR_MODULE_RESOLUTION_LEGACY - google showed up literally nothing.
Okay, so a colleague (thanks #robin-balmforth ) gave me the answer:
From https://nodejs.org/api/esm.html#esm_no_node_path it says:
Notable differences between import and require No NODE_PATH NODE_PATH
is not part of resolving import specifiers. Please use symlinks if
this behavior is desired.
And from something even more canonical:
https://github.com/bmeck/node-eps/blob/es6-module/002-es6-modules.md#521-removal-of-non-local-dependencies says:
All of the following will not be supported by the import statement:
$NODE_PATH $HOME/.node_modules $HOME/.node_libraries $PREFIX/lib/node
Use local dependencies, and symbolic links as needed.
So instead of me setting the NODE_PATH environment variable I have to setup a symlink like:
ln -s ../lib node_modules/lib
Seems to work fine yay.
We suppose the reason is for compatibility with the browser implementation of es6 modules?
There is some discussion on the reasoning behind this change from https://github.com/bmeck in this node-eps issue https://github.com/nodejs/node-eps/issues/11 like:
The solution is not a workaround, it is how path resolution works.
Having a single flat namespace for "bare" path resolution is
important.

create-react-app importing modules as ES6 Modules when project is using CommonJS Modules

I am trying to use CommonJS module format, with create-react-app (CRA). The documentation for CRA says:
..Create React App can consume both CommonJS and ES modules. For Node.js
compatibility, it is recommended that the main entry point is CommonJS...
I am finding that when I import a module that offers ES6 module format (via is "module" property in "package.json"), then CRA uses this as the entry point for the module.
Thus - even when my project is using CJS entirely, it then tries to import ES6 modules - and fails.
So - Is this a bug? Or, am I misunderstanding the intended behaviour of create-react-app?
Reproducing
I have reproduced this here: https://github.com/mjashanks/cra-test. This project is a basic CRA skeleton, modified to used CJS.
Additionally, I have added a "test-module", whose a package.json includes a "main" (CJS) and "module" (ES6) entry point.
If we run npm start, the project fails to build, as the file "test-module/index.esm.js" is being used. (We can edit this file to use CJS format to make the project build and make this more clear).
"test-module/index.cjs.js" is never used.
Thanks :)
I found that this actually turned out to be an issue (actually expected behaviour) with Webpack: https://github.com/webpack/webpack/issues/5756

How to NodeJS require inside TypeScript file?

How do I load a regular NodeJS module (from node_modules) from within a TypeScript class?
When I try to compile .ts file that contains:
var sampleModule = require('modulename');
Compiler prompts that I can't use require in this scope. (That line is at the beginning of the file).
Typescript will always complain when it is unable to find a symbol. The compiler comes together with a set of default definitions for window, document and such specified in a file called lib.d.ts. If I do a grep for require in this file I can find no definition of a function require. Hence, we have to tell the compiler ourselves that this function will exist at runtime using the declare syntax:
declare function require(name:string);
var sampleModule = require('modulename');
On my system, this compiles just fine.
The correct syntax is:
import sampleModule = require('modulename');
or
import * as sampleModule from 'modulename';
Then compile your TypeScript with --module commonjs.
If the package doesn't come with an index.d.ts file and its package.json doesn't have a "typings" property, tsc will bark that it doesn't know what 'modulename' refers to. For this purpose you need to find a .d.ts file for it on http://definitelytyped.org/, or write one yourself.
If you are writing code for Node.js you will also want the node.d.ts file from http://definitelytyped.org/.
The best solution is to get a copy of Node's type definitions. This will solve all kinds of dependency issues, not only require(). This was previously done using packages like typings, but as Mike Chamberlain mentioned, Typings are deprecated. The modern way is doing it like this:
npm install --save-dev #types/node
Not only will it fix the compiler error, it will also add the definitions of the Node API to your IDE.
Use typings to access node functions from TypeScript:
typings install env~node --global
If you don't have typings 😲 install it:
npm install typings --global
when loading typescript module I use module name with extension i.e.
".ts"
const sampleModule = require('modulename.ts');
FYI, in my case "node" command (v14.17.1) using ".ts" files directly without generating "*.js" files.

Resources