Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot import when moduleResolution: node16 is set for typescript #354

Closed
sorgloomer opened this issue Aug 2, 2022 · 22 comments
Closed

Comments

@sorgloomer
Copy link

sorgloomer commented Aug 2, 2022

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: microsoft/TypeScript#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.

@wdavidw
Copy link
Member

wdavidw commented Aug 2, 2022

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.

@sorgloomer
Copy link
Author

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 microsoft/TypeScript#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

@wdavidw
Copy link
Member

wdavidw commented Aug 3, 2022

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.

@sorgloomer
Copy link
Author

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?

@wdavidw
Copy link
Member

wdavidw commented Aug 3, 2022

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.

@mantljosh
Copy link

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)

@wdavidw
Copy link
Member

wdavidw commented Oct 12, 2022

Hum, that is an interesting possibility.

@acidoxee
Copy link

acidoxee commented Oct 29, 2022

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.

@wdavidw
Copy link
Member

wdavidw commented Nov 8, 2022

Does having both the "module" and "moduleResolution" set to "Node16" could resolve the issue ?

@wdavidw
Copy link
Member

wdavidw commented Nov 8, 2022

Otherwise, could someone reproduce the issue in a branch inside a dedicated "./demo/{package}". Reproducing the issue would help finding a solution

@LinusU
Copy link
Contributor

LinusU commented Nov 9, 2022

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'

@wdavidw
Copy link
Member

wdavidw commented Nov 9, 2022

OK, then let's close it.

@wdavidw wdavidw closed this as completed Nov 9, 2022
@acidoxee
Copy link

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.

@wdavidw
Copy link
Member

wdavidw commented Dec 26, 2022

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.

@jonmast
Copy link
Contributor

jonmast commented Feb 7, 2023

@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.

@acidoxee
Copy link

acidoxee commented Feb 7, 2023

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.

@wdavidw
Copy link
Member

wdavidw commented Feb 7, 2023

Validated, could you create a merge request with the demo folder, I'll merge it and make the changes in the mean time

@jonmast
Copy link
Contributor

jonmast commented Feb 7, 2023

I've opened PR #377, thanks for the speedy response @wdavidw!

@wdavidw
Copy link
Member

wdavidw commented Feb 7, 2023

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.

@jonmast
Copy link
Contributor

jonmast commented Feb 7, 2023

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.

@wdavidw
Copy link
Member

wdavidw commented Feb 8, 2023

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

@wdavidw
Copy link
Member

wdavidw commented Feb 8, 2023

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"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants