Skip to content

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

Closed
@sorgloomer

Description

@sorgloomer

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.

Activity

wdavidw

wdavidw commented on Aug 2, 2022

@wdavidw
Member

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

sorgloomer commented on Aug 2, 2022

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

wdavidw commented on Aug 3, 2022

@wdavidw
Member

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

sorgloomer commented on Aug 3, 2022

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

wdavidw commented on Aug 3, 2022

@wdavidw
Member

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

mantljosh commented on Oct 12, 2022

@mantljosh

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

wdavidw commented on Oct 12, 2022

@wdavidw
Member

Hum, that is an interesting possibility.

acidoxee

acidoxee commented on Oct 29, 2022

@acidoxee

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

wdavidw commented on Nov 8, 2022

@wdavidw
Member

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

wdavidw

wdavidw commented on Nov 8, 2022

@wdavidw
Member

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

LinusU

LinusU commented on Nov 9, 2022

@LinusU
Contributor

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

wdavidw commented on Nov 9, 2022

@wdavidw
Member

OK, then let's close it.

acidoxee

acidoxee commented on Dec 23, 2022

@acidoxee

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.

12 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @wdavidw@LinusU@jonmast@sorgloomer@acidoxee

        Issue actions

          Cannot import when `moduleResolution: node16` is set for typescript · Issue #354 · adaltas/node-csv