-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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: true
cause runtime error
#41898
Comments
Update: I found in [checker.ts], there is a checking on Am I digging into the right direction? (https://github.com/microsoft/TypeScript/blob/master/src/compiler/checker.ts) const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); //function canHaveSyntheticDefault
...
// It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member
// So we check a bit more,
if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) {
// If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code),
// it definitely is a module and does not have a synthetic default
return false;
} |
esModuleInterop: true
cause runtime error, suggest: do not encourage to enable it for library projectesModuleInterop: true
cause runtime error
These flags are meant for you to set depending on how your module loader behaves -- if your loader doesn't generate synthetic defaults for you, then it's not correct for you to set Basically this is just a configuration error in your program; you should be setting the flags such that things that don't work at runtime become compile-time errors. It's not meant to create an arbitrary matrix of behavior in any loader environment. |
@RyanCavanaugh Thanks for your reply! var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
For example, var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
}; The result is you cannot access any prototype method of it. // tsconfig:
// "esModuleInterop": true,
// "allowSyntheticDefaultImports": false,
import * as apmStar from 'elastic-apm-node'
import apmDefault from 'elastic-apm-node' // compiling error
console.log(apmStar.start) // runtime error, apmStar is a plain object, has no prototype methods
console.log(apmDefault.start) https://repl.it/@themez1/esModuleInteropTest-1#index.ts I think the problem is that currently
That's what I got so far, if there's anything I misunderstood, please kindly correct me. |
Concisely, what do you think TS should do differently and why? |
When esModuleInterop flag is true:
The reason is if not do so, there would be runtime errors. As #41916 shown, I think the checker is intend to do so for 1, however the implementation is not completed. BTW, I'm glad to help on this issue only if you agree with this approach. (Might need some help to be familiar with checker internals) |
We encountered a similar issue in microsoft/rushstack#2526 . The repro is very easy:
@RyanCavanaugh in our case, setting Setting What's the right way to prevent these mistakes? We never had these problems with |
When writing JavaScript for a commonJS it is natural to do: const colors = require('colors');
// or
const { red } = require('colors'); These intuitively map to: import * as colors from 'colors'; // module.exports
// or
import { red } from 'colors'; // module.exports.red Which is (approximately) what we get when setting Enabling The main problem I see with the flag is that it makes the runtime behavior cease to be statically determinate at compile time, which at the very least should come with some significant warnings in the documentation. For projects that will only ever be executed as CommonJS, "the module exotic namespace object is not a function" is a job for the typings of the particular module, not the compiler in general. I think the only real reason to enable this flag is if you are transpiling the same project to both ESM and CommonJS, will be natively executing both versions in NodeJS (instead of one or the other, for some reason), and therefore need to ensure that the behavior is consistent between the two. Since top-level await was introduced and cannot be transpiled to valid CommonJS, it does seem like a bit of a niche scenario. |
@dmichon-msft I think the reason to enable this flag is to eliminate importing which does not conform to ESM spec. module.exports = class A {} without import * as A from 'a' This does not conform to ESM spec, because an ESM must be a plain object.
import A from 'a' There are so many existing CommonJS library, we have to do so to using them in our ESM projects. |
Note that if you’re using native NodeJS ES modules, then you should set import A from 'a'; will work at runtime. Refs: https://nodejs.org/api/esm.html#esm_commonjs_namespaces |
FYI, this behavior has its own bug reported here: #29687
The docs now warn about the treatment of own vs inherited properties: https://www.typescriptlang.org/tsconfig#esModuleInterop |
I noticed that with
But if you take the same file and move it in to another package, you no longer get an error: import foobar from "@test/foo-lib"; That surprised me! Interestingly, if you add If it's possible to somehow extend the type checking to handle both of these cases consistently (either by emitting the marker into |
@weswigham can you please read the above comment and maybe tell me what you think about the __esModule true marker in the declarations in general? do you see any breaking scenarios with that i ask because i think about adding that to rollup when creating d.ts files. |
It's definitely a guaranteed indicator that the file is a cjs-format module that's masquerading as an es module and has no default presented by ts/babel's import helpers. You shouldn't put it in the declarations for actually-esm format modules, however, since those don't use any import helpers :P |
Hi there, problems happened when I toggled
esModuleInterop
flag for my project, I have to because one library it depends enabled this flag.TypeScript Version: 2.7+
Search Terms:
esModuleInterop
allowSyntheticDefaultImports
Code
Explains:
@sentry/node
it is an es module, soesModuleInterop
has no effects on it, but withallowSyntheticDefaultImports
you can import its default, which property it does not export, so you got an undefined error at runtimeelastic-apm-node
, it is a commonjs module, and it exports an instance,start
is a prototype method of it, so it is lost after__importStar
.But these problems are not informed in the document, guess we could discourage enabling it for library projects?
And I wonder if we could improve type checking for these situations, for example:
apmStar
, it is imported as esmodule namespace, so an type error could be thrown when accessing.start
method on it.SentryDefault
, it is a esmodule,allowSyntheticDefaultImports
could be disabled for itExpected behavior:
Error emitted at compiling time
Actual behavior:
Compiled successfully, but got an error at runtime.
Playground Link:
https://repl.it/@themez1/esModuleInteropTest
Related Issues:
#28009
#33954
#36026
The text was updated successfully, but these errors were encountered: