-
Notifications
You must be signed in to change notification settings - Fork 12.9k
[Fix 13709] - Emit __esmodule #13871
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
Conversation
@@ -378,6 +383,10 @@ namespace ts { | |||
// Append the 'export =' statement if provided. | |||
addExportEqualsIfNeeded(statements, /*emitAsReturn*/ true); | |||
|
|||
if (shouldAppendUnderscoreUnderscoreEsModule) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we set it if we have export =
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't set shouldAppendUnderscoreUnderscoreEsModule
here. This is after we have passed through transform module already and we just check if anyone set the flag
@@ -657,6 +666,8 @@ namespace ts { | |||
} | |||
|
|||
const generatedName = getGeneratedNameForNode(node); | |||
shouldAppendUnderscoreUnderscoreEsModule = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldAppendEsModuleMarker
|
||
const parseTreeNode = getParseTreeNode(node); | ||
if (!shouldAppendUnderscoreUnderscoreEsModule) { | ||
// class declaration get down-level transformed to be variable statement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
declaration -> declarations
statement -> statements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should make any top-level exported declaration of __esModule
an error.
@@ -820,6 +831,14 @@ namespace ts { | |||
let statements: Statement[]; | |||
let variables: VariableDeclaration[]; | |||
let expressions: Expression[]; | |||
|
|||
const parseTreeNode = getParseTreeNode(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this check? Wouldn't setting shouldAppendEsModuleMarker
to true
in createExportExpression
be sufficient? Basically, if I export anything other than via an export=
, then I need to append __esModule
. Would that be correct? Alternatively, always add it if you don't have an export=
(e.g. currentModuleInfo.exportEquals === false
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just setting on in createExportExpression
won't be enough because only VariableStatement with bindingPattern will call into the function.
Also we can't just check for currentModuleInfo.exportEquals === false
because we will be issue extra __esModule even though it doesn't need one
For example:
export enum SignatureFlags {
None = 0,
IsIndexer = 1,
IsStringIndexer = 1 << 1,
IsNumberIndexer = 1 << 2,
}
will get __esModule
as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why wouldn't we emit __esModule here? Perhaps I'm not understanding when we do/don't want to emit this marker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rbuckton After some thought, I think we should emit one here. Though in the following case, I don't think we should. By checking !currentModuleIndo.exportEqual
, we will emit the marker still.
import * as Foo from "Foo"
@@ -21921,6 +21925,22 @@ namespace ts { | |||
return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); | |||
} | |||
|
|||
function checkESModuleMarker(name: Identifier | BindingPattern): boolean { | |||
if (name.kind === SyntaxKind.Identifier) { | |||
if (unescapeIdentifier(name.text) === "__esModule") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error should not be shown in ambient contexts and if --noEmit
is set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we discuss, module kind of system should not show an error as well
src/compiler/diagnosticMessages.json
Outdated
@@ -671,6 +671,10 @@ | |||
"category": "Error", | |||
"code": 1215 | |||
}, | |||
"Identifier expected. '__esModule' is reserved as a marker for transpiled ES2015 module.": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "Identifier expected" is a confusing bit of information that probably shouldn't be reported here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be a better message:
'__esModule' is reserved as a top-level export when transforming ECMAScript modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the new message!. But Identifier expected should be there I think ( similar to how we report similar error for reserved word)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the Identifier expected
is useful in any way. __esModule
is an identifier. 😄
# Conflicts: # src/compiler/transformers/module/module.ts
@@ -88,11 +88,14 @@ namespace ts { | |||
addRange(statements, endLexicalEnvironment()); | |||
addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); | |||
|
|||
if (!currentModuleInfo.exportEquals) { | |||
append(statements, createUnderscoreUnderscoreESModule()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really familiar with TS codebase to know if this is correct and/or easily fixable, but the __esModule
marker should be created as soon as possible. It needs to be visible from circular imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kovensky do you mean __esModule
has to be emitted as a first statement in the output to prevent circular imports? Could you elaborate on your comments? thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yuit not to prevent circular imports, but rather to enable them. Consider:
// a.ts
import b from './b';
export default function(x) {
return x ? b(false) : 1;
}
// b.ts
import a from './a';
export default function(x) {
return x ? a(false) : 2;
}
Both a.ts
and b.ts
should be recognized as an ES module as early as possible, before the first require()
is called in either module.
Consider the following scenario, where we emit __esModule
at the end:
// a.js
var b_1 = require("./b");
exports.default = function (x) {
return x ? b_1(false) : 1;
};
Object.defineProperty(exports, "__esModule", { value: true });
// b.js
var a_1 = require("./a");
exports.default = function (x) {
return x ? a_1(false) : 2;
}
Object.defineProperty(exports, "__esModule", { value: true });
Assuming a.js
is loaded first:
- A module object will be created for
a.js
and stored in Node's module cache. At this point, Node (or rather, some future version) does not know whether it is an ES module or a CJS module. - The
a.js
module body begins to execute anda.js
invokes arequire()
forb.js
. - A module object will be created for
b.js
and stored in Node's module cache. - The
b.js
module body begins to execute the module body and invokes arequire()
fora.js
. - Node cannot see the
__esModule
marker on the exports object fora.js
and doesn't know is an ES module, and therefore cannot provide the right answer tob.js
.
Now consider the same scenario, with the __esModule
marker at the beginning:
// a.js
Object.defineProperty(exports, "__esModule", { value: true });
var b_1 = require("./b");
exports.default = function (x) {
return x ? b_1(false) : 1;
};
// b.js
Object.defineProperty(exports, "__esModule", { value: true });
var a_1 = require("./a");
exports.default = function (x) {
return x ? a_1(false) : 2;
}
Assuming a.js
is loaded first:
- A module object will be created for
a.js
and stored in Node's module cache. At this point, Node (or rather, some future version) does not know whether it is an ES module or a CJS module. - The
a.js
module body begins to execute anda.js
invokes arequire()
forb.js
. - A module object will be created for
b.js
and stored in Node's module cache. - The
b.js
module body begins to execute the module body and invokes arequire()
fora.js
. - Node can see the
__esModule
marker on the exports object fora.js
and knows it can treat it as an ES module.
However, export =
cannot be used circularly to begin with so this whole topic is possibly moot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DanielRosenwasser, is this an accurate description of the mechanics?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rbuckton thank you for the thorough explanation! To be safe, we should then emit __esModule
first. this seems to be safer than emit at the end. However, it will be after some helpers (as those are added by previous transformer)
talk offline with @rbuckton, he gives a green light |
I'm requiring a ts file that has compiled from a commonjs file, how can I check is it export defualt? Is it simply checking whether contain default property? |
Compile such as babel, typescript will compile es module always containing `__esModule`, we should do right things. Typescript 2.2 fix this bug in microsoft/TypeScript#13871
Compile such as babel, typescript will compile es module always containing `__esModule`, we should do right things. Typescript 2.2 fix this bug in microsoft/TypeScript#13871
Compile such as babel, typescript will compile es module always containing `__esModule`, we should do right things. Typescript 2.2 fix this bug in microsoft/TypeScript#13871
Compile such as babel, typescript will compile es module always containing `__esModule`, we should do right things. Typescript 2.2 fix this bug in microsoft/TypeScript#13871
yes. TypeScript does not change any thing about your default export. if the module has a property called |
Fix #13709. It is a big change because of the baselines. The meat of the change is in "module/modules.ts"
Couple question:
What should happen when user already defined "__esmodule"? Currently, we will not emit our version of "__esmodule". Babel gives an error for using such variable name. We will issue an error
Should we emit "__esmodule" for
export import x = require ...
? Yes