TypeScript: Very confusing message when user doesn't realize they're in a CommonJS context

Found from an issue I was diagnosing for @mpodwysocki.

Today, if you use "module": "nodenext" or "module": "node16", you might try to import from a package with ESM files and get the following error message

Module 'yadda/file.js' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.

This is extremely confusing. The fix is typically to add the following field to the package.json that corresponds to the importing file with the error:

    "type": "module"

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 16 (9 by maintainers)

Most upvoted comments

I’m on Typescript 4.9.3 and this doesn’t seem to be fixed yet.

It is VERY confusing. I am glad that I found this thread, and hope the head message can be updated soon.

How about this.


If the file is .cts or .cjs extension:

The current file is a CommonJS module whose imports will produce require() calls; however, the referenced file is an ECMAScript module and cannot be imported via require(). Consider writing a dynamic import() call instead.

Related diagnostics:

The specifier {0} resolved to this file with the path {1}.

If the file does not have an .mts or .mjs extension and there is no type field set in package.json

The current file is a CommonJS module and cannot use this syntax to reference ECMAScript modules. It is likely that your 'package.json' needs a 'type' field set to 'module'.

Related diagnostics:

Consider adding `"type": "module"` to this 'package.json' file.
The specifier '{0}' resolved to this file with the path '{1}'.

If the file does not have an .mts or .mjs extension and there is type field set in package.json

According to this file's 'package.json', it is a CommonJS module and cannot use this syntax to reference ECMAScript modules. This file can be switched to an ECMAScript module by setting its extension to '{0}'.

Related diagnostics:

This file's 'package.json' lives here.
The specifier '{0}' resolved to this file with the path '{1}'.

This becomes especially confusing because it’s an ESM import you’re writing (so the error is actively wrong to say it “cannot be imported using this construct”—it can, with the right package configuration). The error only makes sense when you know the import is being transpiled to require(), and even that’s confusing because it’s not actually a problem with the source code as written but instead the JS output.

I don’t think it’s appropriate to rule out dynamic import in any of these scenarios. I’m planning to use a single head message for all of them:

The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import(\"{0}\")' call instead.

and optionally attaching either

To convert this file to an ECMAScript module, change its file extension to '{0}'.

or

To convert this file to an ECMAScript module, change its file extension to '{0}', or add `\"type\": \"module\"` to its package.json file with path '{1}'.

(We cannot place a related diagnostic on the package.json file itself because it’s not a source file.)

That will not do the trick when importing ESM.

 "compilerOptions": {
    "moduleResolution": "node",
    }

Should do the trick too.

You’re a life saver. Thank you. I did google, but I guess I figured it would’ve been something in the tsconfig. I appreciate your help.

Consider writing a dynamic ‘import("{0}")’ call instead.

Note that this isn’t a valid fix if the tsconfig specifies "module": "node" - then the dynamic import gets converted to require() too.