node-csv
node-csv copied to clipboard
Cannot import when `moduleResolution: node16` is set for typescript
Describe the bug
Getting
main.ts:1:23 - error TS1471: Module 'csv-parse' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.
when trying to import in a project with moduleResolution: node16.
To Reproduce
package.json
{
"dependencies": {
"csv-parse": "5.3.0",
"typescript": "4.7.4"
}
}
tsconfig.json
{
"compilerOptions": {
"moduleResolution": "Node16"
}
}
main.ts
import { parse } from 'csv-parse';
and then run:
tsc
Output I get:
$ tsc
main.ts:1:23 - error TS1471: Module 'csv-parse' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.
1 import { parse } from 'csv-parse';
~~~~~~~~~~~
Found 1 error in main.ts:1
Additional context
My intent in this example is to import csv-parse using require, but typescript seems to think this library only exposes esm modules.
Typescripts esm support is not without controversies. One of them is that they are more strict about the location of .d.ts files: https://github.com/microsoft/TypeScript/issues/49160 . The most straightforward solution seems to be just duplicating the type definitions beside all entry points, but there might be better ways I am not aware of.
In demo/ts-module-node16/tsconfig.json, we used module instead of moduleResolution:
{
"compilerOptions": {
"esModuleInterop": true,
"module": "Node16",
"strict": true,
}
}
Otherwise, please look at the demo and try to find what's different.
Unfortunately having moduleResolution: node16 or nodenext is a hard constraint for us, as this is the only setting of typescript that enables us to use dynamic import() statements that wouldn't get transpiled into require()s. Dynamic imports would be our vehicle to deal with other esm-only modules.
The difference with the new moduleResolutions settings is, to my understanding, typescript now only considers the "exports" entry of the package.json, and does not consider the "types" entry anymore. In https://github.com/microsoft/TypeScript/issues/49160 they claim these typing issues to be due to misconfigured third party packages.
I tried all the typescript configuration combinations, unfortunately none of them is covering all the usecases.
module moduleResolution target message
----------------------------------------------------------
None classic ES5 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
None classic ES2022 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
None node ES5 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
None node ES2022 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
None node16 ES5 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
None node16 ES2022 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
None nodenext ES5 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
None nodenext ES2022 tsconfig.json(9,5): error TS5071: Option '--resolveJsonModule' can only be specified when module code generation is 'commonjs', 'amd', 'es2015' or 'esNext'.
CommonJS classic ES5 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
CommonJS classic ES2022 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
CommonJS node ES5 wrong imports emitted
CommonJS node ES2022 wrong imports emitted
CommonJS node16 ES5 emitted imports OK, but cannot import csv-parse
CommonJS node16 ES2022 emitted imports OK, but cannot import csv-parse
CommonJS nodenext ES5 emitted imports OK, but cannot import csv-parse
CommonJS nodenext ES2022 emitted imports OK, but cannot import csv-parse
ESNext classic ES5 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
ESNext classic ES2022 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
ESNext node ES5 wrong imports emitted
ESNext node ES2022 wrong imports emitted
ESNext node16 ES5 wrong imports emitted
ESNext node16 ES2022 wrong imports emitted
ESNext nodenext ES5 wrong imports emitted
ESNext nodenext ES2022 wrong imports emitted
Node16 classic ES5 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
Node16 classic ES2022 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
Node16 node ES5 wrong imports emitted
Node16 node ES2022 wrong imports emitted
Node16 node16 ES5 emitted imports OK, but cannot import csv-parse
Node16 node16 ES2022 emitted imports OK, but cannot import csv-parse
Node16 nodenext ES5 emitted imports OK, but cannot import csv-parse
Node16 nodenext ES2022 emitted imports OK, but cannot import csv-parse
NodeNext classic ES5 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
NodeNext classic ES2022 tsconfig.json(16,5): error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy.
NodeNext node ES5 wrong imports emitted
NodeNext node ES2022 wrong imports emitted
NodeNext node16 ES5 emitted imports OK, but cannot import csv-parse
NodeNext node16 ES2022 emitted imports OK, but cannot import csv-parse
NodeNext nodenext ES5 emitted imports OK, but cannot import csv-parse
NodeNext nodenext ES2022 emitted imports OK, but cannot import csv-parse
Having a hard time understanding the exact need. I am not a TS user myself, just trying to help. Could you create a demo similar to ts-module-node16. Fork the repository, duplicate the ./demo/ts-module-node16 folder, modify it to suit your need and push the changes in your fork. This would help a lot.
Note, my initial understand was wrong. I have tried to use dynamic import with module with success.
Sure, thanks for considering my case! I am off for a couple of weeks, after that I'll create the fork and will attempt to alter the configuration to match my needs without breaking any of the demos. Do you mind if I keep the ticket open until then?
No worry, I am myself on holidays as well. In order to not break any demo, just cp -rp ./demo/ts-module-node16 ./demo/ts-moduleresolution-node16.
I'm pretty sure the issue here is the type: "module" in package.json which makes TS think that everything in the package is going to be ESM (even though there is CJS as specified in the exports)
Hum, that is an interesting possibility.
I'm having a similar problem as well when using those CSV packages in a CJS context. I'm still not sure that's the whole story, but if I got this right, the issue is that .cjs files in packages/*/dist/cjs MUST have a sibling .d.cts declaration file. Using exports.*.types fields in package.json and pointing them to .d.ts definitions does absolutely nothing, since only .d.cts extensions would work because of the .cjs extension of CJS files.
To reproduce the problem, just do this: https://github.com/acidoxee/node-csv/commit/4e6cf591715a8dc8823989e435b5c079d6ad56a8, and look at the error on the import here: https://github.com/acidoxee/node-csv/blob/4e6cf591715a8dc8823989e435b5c079d6ad56a8/demo/ts-module-node16/lib/stringify.ts#L3.
I don't think there's any use to forcing those exports.*.types anyway (like it was recently added in this commit), since the declarations already seem to always be adjacent to their sibling JS files.
The only appropriate fix here seems to yield matching .d.ts, .d.cts and/or .d.mts declaration files next to each JS file with a .js, .cjs and/or .mjs extension respectfully.
See this comment and its whole thread which seems to confirm this behavior.
Does having both the "module" and "moduleResolution" set to "Node16" could resolve the issue ?
Otherwise, could someone reproduce the issue in a branch inside a dedicated "./demo/{package}". Reproducing the issue would help finding a solution
With #368 merged, the following settings work for us:
tsconfig.json
"module": "ES2022",
"moduleResolution": "node16",
package.json
"type": "module",
my-file.ts
import { parse } from 'csv-parse/sync'
import { stringify } from 'csv-stringify/sync'
OK, then let's close it.
That's great for ESM! Although when consuming csv-parse and csv-stringify from a CJS module, the problem I've described previously is still present AFAICT.
When writing import { parse } from 'csv-parse' from a CJS module with "module": "Node16" in its corresponding TS config, the exports map resolves to "require": "./dist/cjs/index.cjs" for the runtime, but to "types": "./lib/index.d.ts" for the types, which do not have the proper .d.cts extension that's expected for TS to understand it's importing a CJS file (see my previous comment and the TS 4.7 announcement). I believe the reason is that the "type": "module" field in csv-parse's package.json makes TS infer an ESM context everywhere, except if specifically told otherwise through a .d.cts extension on the declaration file.
Wouldn't there be a way for you to generate those declaration files in dist/cjs with a .d.cts extension instead of .d.ts? From my understanding, if you did that, you could (and even should) also entirely remove the exports.*.types field in the export map, since declarations are already adjacent to the JS files, and all imports would work great, whether they're done from ESM or CJS.
If you provide me with a project example illustrating the issue inside the "demo" folder, then I'll be happy to generate the ".d.cts" files.
@wdavidw I've added a demo illustrating the issue: https://github.com/jonmast/node-csv/tree/master/demo/ts-moduleresolution-node16-cjs
Running: npx run typecheck in that directory should return the error.
I believe this happens with the combination of module: commonjs, moduleResolution: node16 (in tsconfig), and module: false (in package.json). The fix is to use .d.cts files as @acidoxee said.
Thanks for the demo @jonmast, and sorry for not getting back to you @wdavidw!
Another thing I haven't tried is putting the exports.*.types key as the first key in the object. This is something that's mentioned in the TS 4.7 announcement here:
Note that the
"types"condition should always come first in"exports".
I'm not sure if this really has an effect or not, but might as well follow their instructions on that matter.
Validated, could you create a merge request with the demo folder, I'll merge it and make the changes in the mean time
I've opened PR #377, thanks for the speedy response @wdavidw!
Could we rename the demo with a short name, also, I don't think node 16 has anything to do with test, I tested the same behavior with Node 18 and 19 and tsconfig mention module as commonjs as target as es5.
Sure, I can rename it.
You are correct that the error isn't node version specific, it is from Typescript's "node16" resolver which was built to be compatible with the new module resolver in node 16 but also applies to newer releases.
The following packages were updated:
- csv-generate: 4.2.1 => 4.2.2
- csv-parse: 5.3.4 => 5.3.5
- csv-stringify: 6.2.3 => 6.2.4
- csv: 6.2.6 => 6.2.7
- stream-transform: 3.2.1 => 3.2.2
Sure, I can rename it.
You are correct that the error isn't node version specific, it is from Typescript's "node16" resolver which was built to be compatible with the new module resolver in node 16 but also applies to newer releases.
I renamed it to "ts-cjs-node16"