Skip to content
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

ES6 module variables are copied when re-exported #6366

Closed
jonaskello opened this issue Jan 5, 2016 · 4 comments · Fixed by #35967
Closed

ES6 module variables are copied when re-exported #6366

jonaskello opened this issue Jan 5, 2016 · 4 comments · Fixed by #35967
Labels
Breaking Change Would introduce errors in existing code ES6 Relates to the ES6 Spec In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@jonaskello
Copy link

Given this example:

a.ts

export var a = 1;
export function changeA() { a = 2; }

b.ts

export * from './a';

c.ts

import { a, changeA } from './b';
console.log(a); // 1
changeA();
console.log(a); // Expected 2 but get 1

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "out"
  }
}

Result of runing c.js:

$ node out/c.js
1
1

This happens because in b.ts this:

export * from './a';

transpiles to

function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(require('./a'));

and the value of variable a is copied and not referenced.

If I run the same example through babel it works because it generates getters that references the original variable. This was also proposed here.

I propose that if the target is set to es5 then the generated code for commonjs modules uses getters instead to make sure that re-exported variables are referenced instead of copied.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Jan 5, 2016
@RyanCavanaugh
Copy link
Member

@mhegazy we should discuss this ASAP (Friday?)

@Arnavion
Copy link
Contributor

Isn't this "Bug" instead of "Suggestion" ? :)


For re-exports from modules with relative paths, TS could avoid the tax of getters and maintain ES3 support by rewriting the import of the re-export to use the original export, eg import { a, b } from "./b"; console.log(a, b); could compile to var a = require("./a"); var b = require("./b"); console.log(a.a, b.b);. (This is similar to what rollup does when it merges modules.) But it only works for modules with relative paths, not for re-exports from node_modules, etc., so it may be of limited value.

Edit: To clarify, the codegen for the re-export itself would still have to use a getter defined on exports. Just any other TS code that imports it could be compiled to refer to the original export instead.

@mhegazy mhegazy added the ES6 Relates to the ES6 Spec label Feb 24, 2016
@camillol
Copy link

camillol commented Sep 1, 2019

This issue has been dormant since 2016, but it's still valid. And it seems like it really should be labeled as a bug.

As far as I can tell, re-exports are supposed to be live views in ES6, and a re-exported variable is supposed to reflect changes. Looking at the code generation examples here, TypeScript exports map directly to ES6/ES 2015 modules when targeting ES 2015, so they will have the correct ES6 semantics in that case, right? Which makes it even more undesirable for them to have different semantics when transpiling to ES3.

At any rate, regardless of the current behavior of different compilation targets, it seems like the behavior of TypeScript modules should eventually align with that of ES6 modules. One advantage of having native module support should be that there is a common, clearly defined semantics for module import/export.

@stephenh
Copy link

stephenh commented Dec 11, 2019

I'm running into this bug while using the barrel pattern, i.e. in an index.ts:

export * from "./Entity1";
export * from "./Entity2";
export * from "./Entity3";

Then later:

import { Entity1, Entity2, Entity3 } from "entities";

someFunction() {
  const entities = [Entity1, Entity2, Entity3]
  // Entity3 is undefined
}

If I change it to:

import { Entity1, Entity2 } from "entities";
import { Entity3 } from "entities/Entity3";

Then it works and Entity3 is no longer undefined.

I do have some cross-entity imports (i.e. Entity3 pulls in Entity2) that is triggering a circular loading issue, but, once my someFunction runs, live bindings would mean everything should be settled down and defined by that point.

Also, I'm technically running tests via ts-jest but I assume this is the same underlying issue in the "re-exports are not live bindings".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Breaking Change Would introduce errors in existing code ES6 Relates to the ES6 Spec In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
7 participants