Repository navigation
[Bug] Re-exported binding doesn't track the original binding #12522
Description
TypeScript Version: nightly (2.2.0-dev.20161127)
EDIT: The issue described below is with --module commonjs, but as Aluan Haddad (@aluanhaddad) points out in a comment below, there are other behaviours with different module emits.
I looked for an existing issue about this but didn't find one. Apologies if this is a duplicate.
I ran into a problem with TypeScript's emit for re-exported bindings, in particular when the original exported value changes over time.
Importing a binding directly from a source module gets a 'live view' that changes when the export value changes, as it should. But importing from a re-exported binding gets a snapshot that is no longer a live view of the original export. This seems not to be spec compliant and leads to my code not working as intended.
Code
// file: a.ts
export let liveView = 'foo';
export function changeTo(val: string) { liveView = val; } // CHANGES THE EXPORT VALUE
// file: b.ts
import {liveView, changeTo} from './a'; // <===== Import directly from A
console.log(liveView); // outputs 'foo'
changeTo('bar');
console.log(liveView); // outputs 'bar' <===== GOOD: binding changed as it should
export {liveView, changeTo} from './a'; // <===== Re-export from A
// file: c.ts
import {liveView, changeTo} from './b'; // <===== Import via B's re-export
console.log(liveView); // outputs 'bar'
changeTo('baz');
console.log(liveView); // outputs 'bar' // <===== BAD: binding is not live anymoreExpected behavior
Emitted code should preserve the live binding to liveView when re-exporting from b.ts.
Last line of c.ts should output 'baz'.
Actual behavior
Emitted code exports a 'snapshot' of the liveView binding from b.ts, so that the import of liveView into c.ts is no longer a live binding.
Last line of c.ts incorrectly outputs 'bar'.
What should be the Correct Behaviour?
I found it hard to locate a clear description of correct ES6 behaviour for this situation, but from what I did find I think TypeScript's current emit is not compliant with ES6.
-
E.g. from the Exploring ES6 sections about imports as views and about re-exports
-
Babel emits code that preserves the 'live-ness' of re-exports
So for export { liveView } from 'some-module'; TypeScript emits something like:
var some_module_1 = require("some-module");
exports.liveView = some_module_1.liveView;Whereas babel emits something like:
var _someModule = require('some-module');
Object.defineProperty(exports, 'liveView', {
enumerable: true,
get: function get() {
return _someModule.liveView;
}
});
Activity
[-]Emit for re-exports loses live binding (not ES6 compliant)[/-][+][Bug] Re-exported binding doesn't track the original binding[/+]aluanhaddad commentedon Dec 4, 2016
This is really interesting. Note that is works differently in different module formats. Specifically if you compile into
systemmodules, the output is actuallyas you expect it to be because the vars are declared outside of the closure which sets up the module and are hoisted. Also, using any module format except
systemresults in output which importsatwice inb. Another curious aspect is how reordering the statements changes the behavior.If we alter
bby moving the re-export above the call tochangeToand transpile to
commonjs, the output changes tothe behavior under the
systemformat does not change.yortus commentedon Dec 4, 2016
That is interesting. I should have mentioned in the OP that I was using
commonjs. Added it now.yortus commentedon Dec 8, 2016
This seems to have slipped through the cracks. Mohamed Hegazy (@mhegazy) would you mind triaging this issue?
aluanhaddad commentedon Feb 20, 2017
The current state of the node-eps for es modules makes this very pertinent. It definitely needs to be revisited.
squidfunk commentedon Jan 22, 2019
Any update on this? Still happening and wildly indeterministic. Also, when I add a simple
console.logstatement or even an empty comment to the file that re-exports everything inside that folder, it magically works:Doesn't work (
foois not re-exported):Works:
Also works:
I'm also using
commonjs.EDIT: Following this comment – it seems to vanish when removing
"strict": truefromtsconfig.json. I added all flags that are enabled by"strict": truemanually.squidfunk commentedon Mar 16, 2019
t532 - removing
strict: truestill works for me with TypeScript (3.3.3333)squidfunk commentedon Mar 25, 2019
Unfortunately t532 is right - I've just encountered a case where setting
strict: truedoesn't work. Needs investigation.squidfunk commentedon May 3, 2019
What's the state of this issue? Is it considered being investigated?
The issue was triaged as "needs investigation" by Ryan Cavanaugh (@RyanCavanaugh) nearly two years ago, and shortly after that labeled as a "bug", but a year ago it was not considered being a bug anymore? Its effects are wildly indeterministic and thus really annoying. We're in need of deterministic builds and re-exports are a very common thing. This problem may live in a lot of codebases.
I'm currently working around it by adding an empty comment after the
exportstatement of the dependency that is not correctly being re-exported, but that's far from a solution.squidfunk commentedon Jun 1, 2019
I started a PR in #31715 using the technique described in #13245 (comment). Heavily WIP. Happy to collaborate!