Skip to content

esModuleInterop should work even when compiling to esnext modules #22851

Open

Description

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

Activity

  1. DanielRosenwasser commented on Mar 24, 2018

    @DanielRosenwasser
    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 Wesley Wigham (@weswigham) might have better insight.

  2. Jessidhia commented on Mar 27, 2018

    @Jessidhia

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

  3. aryzing commented on Apr 8, 2018

    @aryzing

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

    Daniel Rosenwasser (@DanielRosenwasser) Doesn't it already? From --esModuleInterop docs

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

  4. jamiebuilds commented on Apr 9, 2018

    @jamiebuilds
    Author

    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

  5. ExE-Boss commented on Jan 31, 2020

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

  6. teppeis commented on Jan 26, 2021

    @teppeis

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

  7. bhvngt commented on Aug 12, 2021

    @bhvngt

    Hi, Just checking is this issue has been resolved? I am facing a similar as was raised by Jamie Kyle (@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.

  8. teppeis commented on Aug 27, 2021

    @teppeis

    const_var (@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

  9. bhvngt commented on Aug 27, 2021

    @bhvngt

    Thanks Teppei Sato (@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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: ES ModulesThe issue relates to import/export style module behaviorIn DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions