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
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.
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".
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.
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
Do note that Node 12.x only supports the default import when importing CommonJS modules from ESModules:
import legacy from "cjs-package";
This doesn鈥檛 work:
import { method } from "cjs-package";
The only exception to that are built鈥慽n modules, which have special handling.
Most helpful comment
@DanielRosenwasser Doesn't it already? From
--esModuleInteropdocsEmit聽__importStar聽and聽__importDefault聽helpers for runtime babel ecosystem compatibility and enable聽--allowSyntheticDefaultImportsfor typesystem compatibility.