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

esModuleInterop should work even when compiling to esnext modules #22851

Open
jamiebuilds opened this issue Mar 24, 2018 · 9 comments
Open

esModuleInterop should work even when compiling to esnext modules #22851

jamiebuilds opened this issue Mar 24, 2018 · 9 comments
Labels
Domain: ES Modules The issue relates to import/export style module behavior In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@jamiebuilds
Copy link

jamiebuilds commented Mar 24, 2018

TypeScript Version: 2.7.2
Search Terms: esModuleInterop, esnext, modules, import, export, default

Code

With this type definition:

declare function fn(): void;
declare module "external" {
  export = fn;
}

Running with:

tsc --esModuleInterop --module esnext

Produces these errors when importing:

import fn1 from 'external';       // error TS1192: Module '"external"' has no default export.
import fn2 = require('external'); // error TS1202: Import assignment cannot be used when targeting ECMAScript modules.

But, if you use commonjs modules:

tsc --esModuleInterop --module commonjs

It works as expected (because of --esModuleInterop)

import fn1 from 'external';       // works
import fn2 = require('external'); // works

Expected behavior:

It is understandable that the type checker doesn't want to pretend the import is interop'd when it's not compiling in the helpers.

But if you've specified --esModuleInterop and --module esnext the assumption from the type checker should be that an external system is applying the interop. Otherwise why would you specify --esModuleInterop?

Playground Link: https://github.com/jamiebuilds/ts-bug

@DanielRosenwasser
Copy link
Member

The question is really about what exactly to emit. When you want to emit to ES modules, a default import really needs to continue being a default import. So esModuleInterop is both about the type-checking as well as the default.

On the other hand, when you expect an external tool (e.g. Babel, Webpack, SystemJS) to stitch the ES interop on its own, that's when you can use allowSyntheticDefaultImports.

I guess you could make the argument that esModuleInterop should imply allowSyntheticDefaultImports and just emit nothing, but @weswigham might have better insight.

@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript In Discussion Not yet reached consensus Domain: ES Modules The issue relates to import/export style module behavior labels Mar 24, 2018
@Jessidhia
Copy link

The problem with --allowSyntheticDefaultImports is that it'd still treat import * as foo from 'cjs' the same as import foo from 'cjs'. The ideal behavior would be something like "--requireSyntheticDefaultImports".

@aryzing
Copy link

aryzing commented Apr 8, 2018

I guess you could make the argument that esModuleInterop should imply allowSyntheticDefaultImports

@DanielRosenwasser Doesn't it already? From --esModuleInterop docs

Emit __importStar and __importDefault helpers for runtime babel ecosystem compatibility and enable --allowSyntheticDefaultImportsfor typesystem compatibility.

@jamiebuilds
Copy link
Author

jamiebuilds commented Apr 9, 2018

allowSyntheticDefaultImports does not fix the issue. It also has broken behaviour when you re-export something that was imported with it: You get an object with the shape { default: T } instead of T

@ExE-Boss
Copy link
Contributor

Do note that Node 12.x only supports the default import when importing CommonJS modules from ESModules:

import legacy from "cjs-package";

This doesn’t work:

import { method } from "cjs-package";

The only exception to that are built‑in modules, which have special handling.

@teppeis
Copy link

teppeis commented Jan 26, 2021

This issue seems to have been resolved in TypeScript 3.2 3.1 or later.

@bhvngt
Copy link

bhvngt commented Aug 12, 2021

Hi, Just checking is this issue has been resolved? I am facing a similar as was raised by @jamiebuilds here

I am using a library that is exporting a function as default. While importing I get two different object representation of the imported object based on whether my typescript project is setup as esm or commonjs.

Project sandbox can be found here.

TS Project setup as commonjs

  • "type": "commonjs" in package.json
  • "module": "commonjs" in tsconfig.json
  • Running node -r ts-node/register src/index.ts
  • I get [Function: standaloneCode] as output which is the expected output

TS Project setup as esm

  • With "type": "module" in package.json
  • "module": "esnext" in tsconfig.json
  • Running node --loader ts-node/esm src/index.ts
  • I get { default: [Function: standaloneCode] }. This messes up my type definitions.

I have tried with "esModuleInterop": true and "allowSyntheticDefaultImports": true. But that does not seem to be making much of a difference.

Any workaround or suggestion will be very much appreciated.

@teppeis
Copy link

teppeis commented Aug 27, 2021

@bhvngt Your problem is different from this issue. It is the current TypeScript specification.
ajv's main has a special hack that makes it readable from both TS commonjs and esm,
https://github.com/ajv-validator/ajv/blob/8ffe5faca9c4b49fc538b35399a1b4febba0bc41/lib/ajv.ts#L34-L35
but your ajv/dist/standalone/index.js doesn't have such a hack.
https://github.com/ajv-validator/ajv/blob/8ffe5faca9c4b49fc538b35399a1b4febba0bc41/lib/standalone/index.ts

@bhvngt
Copy link

bhvngt commented Aug 27, 2021

Thanks @teppeis. Got it. Will raise this issue in ajv repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: ES Modules The issue relates to import/export style module behavior In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants