React Vite: Exports from local modules not found (Migration from CRA) - node.js

I am trying to import exported members from local nodejs packages. These worked fine before using Vite (standard CRA webpack setup), but ever since switching to Vite, I am getting the following error:
Uncaught SyntaxError:
The requested module '/#fs/Users/...{long_path}.../lib/shared/countries/build/index.js'
does not provide an export named 'countryListAllIsoData'
The Vite project was setup using npm create vite#latest using the typescript, react, swc preset.
These failed imports only happen on local packages (none installed from the npm registery) so I am sure the issue is on my end, but I have not been able to track down what is causing it.
Question 1: How can I make these imports work?
As they did work using Webpack, I'm not quite sure why they don't work using Vite? Vite required my project to setup the project as a module (in package.json - "type": "module"). Does the way imports work in a module break my current code?
Question 2: How can I make my linter display these errors?
I am using VS Code which seems to be perfectly happy with the way the module is imported. No errors are shown and I can go to the definition of the imports without any problems (leading me to believe everything is exported correctly).
Relevant code snippets
I am importing as follows:
import { countryListAllIsoData } from "countries";
The tsconfig.json file is untouched (exactly as npm create vite#latest created). The same applies for the package.json file, except some modules were installed (e.g. the one causing this issue).
The local package causing this issue is named countries and is fairly small and simple. It has the following package.json file:
/// package.json
{
"name": "countries",
"version": "1.0.0",
"description": "",
"main": "./build/index.js",
"exports": {
".": "./build/index.js"
},
"scripts": {
"build": "tsc",
"prepare": "npm run build"
},
"keywords": [],
"author": "Bram Vanbilsen",
"license": "UNLICENSED",
"devDependencies": {
"typescript": "^4.8.4"
}
}
The tsconfig.json file:
{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declarationMap": true, /* Create sourcemaps for d.ts files. */
"outDir": "./build", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
The index.ts file:
export const countryListAllIsoData = [...] as const;

Related

NPM Package: Conflicts between ES libraries

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 ?

Compile with tsc run with node, ReferenceError: exports is not defined in ES module scope

This question has been asked many times before, but I can not find a solution
I'm using typescript in my project.
I have my index.ts file which contains code like this:
import { getEnvOptions } from './env/envreader';
import express, {Express, Request, Response} from 'express';
When I compile this with command tsc and try to run it with node
I get an error saying:
Object.defineProperty(exports, "__esModule", { value: true });
ReferenceError: exports is not defined in ES module scope
As I understand the problem is that the node runtime environment doesn't recognize the keyword "exports" that is generated by the typescript compiler. I intend to run this project on a server and not in a browser which is why I set the "module" as "CommonJS".
Here's the tsconfig:
{
"compilerOptions": {
"target": "ES5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "CommonJS", /* Specify what module code is generated. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"noEmit": false, /* Disable emitting files from a compilation. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["./**/*.ts"],
}
What exactly should I do to make this run in a node environment if I want to keep using the typescript import statements?
Remove "type": "module" from package.json since you can use imports without specifying that in package.json, the reason is because import is part of typescript.

Should I use commonjs or es module for yarn workspace sub packages for next.js proejct?

I'm building Next.js monorepo project with TS, yarn workspace.
For example, I have two packages in yarn workspace, /web and /api. /web is a next.js project and /api is a shared subpackage that is used by /web.
/my-project <-- project root
package.json
/web
src/
package.json
tsconfig.json
next.config.js
/api
src/ <-- rootDir
dist/ <-- outDir
package.json
tsconfig.json
...
// /my-project/package.json
{
"private": true,
"workspaces": [
"web",
"api"
],
}
// /web/packcage.json
{
"dependencies": {
"#api": "workspace:*"
}
}
// /api/packcage.json
{
"name": "#api"
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
}
// /api/tsconfing.json
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "ES6",
"moduleResolution": "node",
"target": "ES6",
},
}
From /api's tsconfig, TS creates transpiled result which has es6 module system.("module": "ES6")
As nextjs does not support external packages built with ES module, I expected that /api package does not work in /web project. However, it works well.
Why this can be possible?
When I tried to use some packages which use ES module(built only for browser), I met some errors something like unexpected token: export. At that time, I have to transpile them
manually by using next-transpile-modules and then it resolved the issue. But, in this case, nextjs work with es module package without any issue. Did I misunderstand something about this?
NextJs 11.1+ has an experimental option to support esm. The feedback thread is available here.
/**
* #type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
// Prefer loading of ES Modules over CommonJS
// #link {https://nextjs.org/blog/next-11-1#es-modules-support|Blog 11.1.0}
// #link {https://github.com/vercel/next.js/discussions/27876|Discussion}
esmExternals: true,
// Experimental monorepo support
// #link {https://github.com/vercel/next.js/pull/22867|Original PR}
// #link {https://github.com/vercel/next.js/discussions/26420|Discussion}
externalDir: true,
}
}
export default nextConfig;
ESM generally requires more than just "modules": "es6" in config... But as you noticed, I realized that some esm packages like '#sindrehorsus' ones where working without the experimental flag.
That said setting the experimental.esmExternals to true will solve most edge cases and should makes next-transpile-modules obsolete.
Note that generally I tend to set "modules" to "esnext" rather than "es6" to be inline to what nextjs recommends.
PS: If it helps, you can have a look this comment: https://stackoverflow.com/a/69554480/5490184 or the https://github.com/belgattitude/nextjs-monorepo-example.

Absolute path in the tsconfig doesn't work

I saw some questions about this problem, none of them does not work
I have a nodejs project along with Typescript. I do not like to use a relative path.I get the following error, when I set path in tsconfig :
Cannot find module '#app/controllers/main'
// main.ts
export const fullName = "xxxx";
...
// app.ts
import { fullName } from '#app/controllers/main'
...
This is the structure of my project :
-node_modules
-src
----controllers
---------------main.ts
----app.ts
-package.json
-tsconfig.json
tsconfig:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"baseUrl": ".",
"paths": {
"#app/*": ["src/*"]
},
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Where is my problem?
Thanks in advance.
Update 2023
Install development dependencies
npm install --save-dev \
ts-patch \
typescript-transform-paths \
tsconfig-paths
ts-patch
Directly patch typescript installation to allow custom transformers (plugins).
The main difference why I prefer ts-patch over ttypescript is that there is no need to change the compiler (ttsc) because (hence the name) tsc is patched.
typescript-transform-paths
Transforms absolute imports to relative from paths in your tsconfig.json.
tsconfig-paths
Load modules whose location is specified in the paths section of tsconfig.json. Both loading at run-time and via API are supported.
Update tsconfig.json
Note: See paths and plugins
{
"compilerOptions":{
/* A series of entries which re-map imports to lookup locations relative to the baseUrl */
"paths":{
"~/*":[
"./src/*"
]
},
/* List of language service plugins */
"plugins":[
/* Transform paths in output .js files */
{
"transform":"typescript-transform-paths"
},
/* Transform paths in output .d.ts files */
{
"transform":"typescript-transform-paths",
"afterDeclarations": true
}
]
}
}
Patch Typescript
Note: This is NOT persistent
npx ts-patch install
Edit/Add prepare script in package.json to patch Typescript persistently
Note: This IS persistent
{
// ...
"scripts": {
"prepare": "npx ts-patch install -s"
}
}
Usage in import
import { hello } from '~/world';
Compile as always
npx tsc
Old Answer
Unfortunately (and I don't know why) the Typescript compiler currently does not support the paths transformation very well.
Here is my solution:
I used the solution with this project.
Install devDependencies
ttypescript -> npm install ttypescript --save-dev -> TTypescript (Transformer TypeScript) solves the problem by patching on the fly the compile module to use transformers from tsconfig.json.
typescript-transform-paths -> npm install typescript-transform-paths --save-dev -> Transforms absolute imports to relative from paths in your tsconfig.json.
tsconfig-paths -> npm install tsconfig-paths --save-dev -> Use this to load modules whose location is specified in the paths section of tsconfig.json. Both loading at run-time and via API are supported.
ts-node-dev -> npm install ts-node-dev --save-dev -> It restarts target node process when any of required files changes (as standard node-dev) but shares Typescript compilation process between restarts
tsconfig.json
Update the tsconfig.json file with the following options:
{
"compilerOptions": {
/* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"paths": {
"#app/*": [
"./src/*"
]
},
/* Advanced Options */
"plugins": [
{
"transform": "typescript-transform-paths"
}
],
}
}
Build
For the compilation phase use ttsc instead of tsc with the configuration file. See the snippet below:
npx ttsc --p ./tsconfig.json
Development mode with autoreload
When you are in dev mode use the following script (put it in the scripts options in package.json) to automatically reload the project with the correct paths. The src/app.ts is the "entry point" of your application located under the src folder.
npx ts-node-dev --prefer-ts true --no-notify -r tsconfig-paths/register --watch src --transpileOnly src/app.ts
PS: Using ts-node-dev increase the speed significantly.
I tweak #Carlo Corradini answer a little bit,
here is my approach to fix this issue.
install required plugins:
npm i -D ttypescript typescript-transform-paths ts-node tsconfig-paths. these are packages that will help us to transform the paths.
ttypescript-> https://www.npmjs.com/package/ttypescript
then, on tsconfig.json, I put:
{
"ts-node": {
"transpileOnly": true,
"require": [ // set this so you dont need to use ts-node -r
"typescript-transform-paths/register",
"tsconfig-paths/register"
]
},
"compilerOptions": {
"composite": true,
"rootDir": ".", // must define
"baseUrl": "src", // must define, the paths will relative to this
"outDir": "lib",
"skipLibCheck": false,
"paths": {
"#app/*": ["./*"],
"#controllers/*": ["./controllers/*"],
},
"plugins": [
{ "transform": "typescript-transform-paths" }
]
}
}
then on your codes you can use:
import myControllers from "#controllers/main.ts"
now you can compile, npx ttsc -p tsconfig.json
and to run on ts-node use npx ts-node -p tsconfig.json src/app.ts
Your syntax is correct.
It appears that, at the time of writing, ts-node-dev does not support the paths entry of tsconfig.json.
This github issue discusses the problem and presents workaround options.

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