Typescript: esModuleInterop not working for certain libraries

Created on 23 Sep 2018  路  13Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.1.0-dev.20180922


Search Terms: esModuleInterop error undefined default import allowSyntheticDefaultImports

Code

{
    "compilerOptions": {
        "alwaysStrict": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "target": "es2016",
        "module": "commonjs",
        "moduleResolution": "node"
    }
}
{
  "dependencies": {
    "tslint": "^5.11.0",
    "typescript": "^3.1.0-dev.20180922",
    "vue-template-compiler": "^2.5.17"
  }
}
import tslint from 'tslint';
import vueCompiler from 'vue-template-compiler';

console.log(tslint.Linter !== undefined);
console.log(vueCompiler.compile !== undefined);

Expected behavior:

No error.

Actual behavior:

console.log(tslint_1.default.Linter !== undefined);
                             ^

TypeError: Cannot read property 'Linter' of undefined
    at Object.<anonymous> (C:\VS\esmoduleinterop\index.js:8:30)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)
console.log(vue_template_compiler_1.default.compile !== undefined);
                                            ^

TypeError: Cannot read property 'compile' of undefined
    at Object.<anonymous> (C:\VS\esmoduleinterop\index.js:8:45)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:279:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:752:3)

Playground Link:

Related Issues:

https://github.com/Microsoft/TypeScript/issues/21621

Possible Solution:

Those libraries are happened to be native ES modules, but has no default export...

var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};

The above TypeScript-generated helper method should check for mod.hasOwnProperty('default') too:

var __importDefault = (this && this.__importDefault) || function (mod) {
    if (mod && mod.__esModule) {
        if (mod.hasOwnProperty('default') === false) {
            mod.default = mod;
        }
        return mod;
    } else {
        return { "default": mod };
    }
};
Question

Most helpful comment

@ishan123456789 sorry for the late reply, you can import the said library using import = syntax:

import MyLibrary = require('my-library');

All 13 comments

That's interesting. I would have expected a default import from an ES module with no default export to be a compile error even with allowSyntheticDefaultImports but it isn't, perhaps because TypeScript can't distinguish a declaration file for a traditional CommonJS module from one for an ES module compiled to CommonJS. So there's an inconsistency between TypeScript's compile-time and runtime behavior, and the only option to fix it may be to allow a default import at runtime from an ES-compiled-to-CommonJS module with no default export to import the entire module, as you propose.

because TypeScript can't distinguish a declaration file for a traditional CommonJS module from one for an ES module compiled to CommonJS

This is indeed the root cause. At some point we need syntax to say "This is really an ES6 module" in a declaration file (basically a .d.ts representation of the existence of __esModule).

@RyanCavanaugh Thanks for the update. If you agree there is a problem, why not turn this issue into a bug or suggestion to resolve the problem rather than closing it as a question and forcing the next person who cares to file another issue?

Ideally someone logs a bug with a clear problem statement. Having the OP be someone's question with stack traces, etc, makes it confusing later down the line

This is indeed the root cause. At some point we need syntax to say "This is really an ES6 module" in a declaration file (basically a .d.ts representation of the existence of __esModule).

Why can't we follow webpack / Babel's standard behavior where if an ES module being imported as default import but has no default export, the default import will be resolved as * import?

I thought the esModuleInterop was designed with that case in mind?

Ideally someone logs a bug with a clear problem statement. Having the OP be someone's question with stack traces, etc, makes it confusing later down the line

I apologize if the OP wasn't clear with the problem statement, I was just following TypeScript's new bug issue template format to the letter.

How would you like me to alter the OP so that this issue can be categorized as bug?

Why can't we follow webpack / Babel's standard behavior where if an ES module being imported as default import but has no default export, the default import will be resolved as * import?

That isn't Babel's behavior either!

If you have an ES6 module (as indicated by the presence of __esModule) and you import it with ES6 module syntax, then cooking up a default export out of nowhere is 110% wrong, not to mention very likely to break your code in the future if that module adds a default export later. esModuleInterop is not here to say "Let's make defaults where none exist" - it's here to provide ES6-like behavior when importing CommonJS modules.

Anyway, turning on esModuleInterop when you're importing ES6 modules and targeting CJS is technically a user error. But you might be legitimately mixing some combination of ES6 and CJS modules, and have no way to indicate which modules are eligible for default-synthesizing and which aren't. So the "bug" here is a feature request for a way to annotate a module as "Actually ES6" or maybe "Definitely CJS", which would allow TS to correctly flag the missing default export of the specified module instead of assuming it's actually CJS at runtime.

Anyway, turning on esModuleInterop when you're importing ES6 modules and targeting CJS is technically a user error.

Thank you for the guidance. That should probably be warned somewhere when tsconfig.json module is set to commonjs and esModuleInterop is turned on...

I was under impression that it was designed for when you are still targeting CJS:

https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/

To give users the same runtime behavior as Babel or Webpack, TypeScript provides a new --esModuleInterop flag when emitting to legacy module formats.

Using esnext modules with --esModuleInterop really only has the effect of turning on --allowSyntheticDefaultImports.

That should probably be warned somewhere when tsconfig.json module is set to commonjs and esModuleInterop is turned on

Key clause: when you're importing ES6 modules. If you only ever import ES6 modules, then esModuleInterop can only make your life worse. But again we have no way to know if the modules you're importing are CJS or ES6.

Ideally someone logs a bug with a clear problem statement. Having the OP be someone's question with stack traces, etc, makes it confusing later down the line

If you insist, #27329.

I'm getting This module is declared with using 'export =', and can only be used with a default import when using the 'esModuleInterop' flag error when I turn down esModuleInterop flag. @ryanelian can you please help me fix that.

@ishan123456789 sorry for the late reply, you can import the said library using import = syntax:

import MyLibrary = require('my-library');

@ryanelian that's all fun and all, but what to do if emitDeclarationOnly: true and tsc generates import MyLibrary from 'my-library' instead of import MyLibrary = require('my-library') in index.d.ts file?

Was this page helpful?
0 / 5 - 0 ratings