Recursive Node.js require
See original GitHub issueRecursive modules with exporting files in-between don’t work with TypeScript because export { ... } from '...'
statements extract the exported property when the file first runs instead of using a getter to lazily extract the exported property as Babel does.
When Node.js detects recursive modules, they emit the module exports that have been initialized thus far. See module cycles documentation. This means an export may be undefined
in the require
d module when a cycle is detected. However, that export will later be defined when both modules finish their initial execution. Because TypeScript does not use getters there is no way of getting the export when it is later defined.
Here is a concrete example to demonstrate the recursive problem in Node.js. In the bug template is the output expected for a given file. The below example would create an infinite recursive loop when a
or b
is called. This is the desired behavior. What actually happens is that either b
or a
is undefined and can’t be called.
./a/foo.ts
import { b } from '../b'
export function a () {
b()
return 'foo'
}
./a/index.ts
export { a } from './foo'
./b/bar.ts
import { a } from '../a'
export function b () {
a()
return 'bar'
}
./b/index.ts
export { b } from './bar'
Expected/Actual
TypeScript Version: 2.1.4
Code
export { a } from './foo'
export { b } from './bar'
Expected behavior:
Emits getters for CommonJS exports like Babel:
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _foo = require('./foo');
Object.defineProperty(exports, 'a', {
enumerable: true,
get: function get() {
return _foo.a;
}
});
var _bar = require('./bar');
Object.defineProperty(exports, 'b', {
enumerable: true,
get: function get() {
return _bar.b;
}
});
Actual behavior:
Extracts the imported values on file execution so if the reference changes in the required module, that change does not propagate:
"use strict";
var foo_1 = require("./foo");
exports.a = foo_1.a;
var bar_1 = require("./bar");
exports.b = bar_1.b;
Issue Analytics
- State:
- Created 7 years ago
- Reactions:3
- Comments:9 (4 by maintainers)
Top GitHub Comments
Ok but inherent problems aside I don’t see anything that really blocks changing the emit behavior from this:
To this:
This results in the exact same behavior that is being emit now. Technically you can’t configure or set to these properties (I’m not sure why anyone would), but if you wanted strict backwards compatibility you just change the emit to this:
Then we really do have the exact same behavior.
So this would not change the TypeScript interpretation of the ES Module and CommonJS module systems. After all it behaves the same. The only thing this changes is it fixes an inconsistency in how TypeScript handles the ES Module spec specifically in the case of recursive Node.js requires.
If you worry about adding bytes to your application code, then the state of the art for JavaScript bundling is to not transpile to CommonJS and instead let your bundler (Webpack or Rollup most likely) to intelligently handle your ES Module declarations (and it’s worth noting that they handle this case as well).
I guess from my perspective I’m not seeing any inherent problems. This seems like a net positive to me that does not effect any existing code and fixes a specific bug that I (and likely Babel users too given Babel’s behavior) have been experiencing 😖
I started a PR in #31715 using the technique described by @calebmer in https://github.com/microsoft/TypeScript/issues/13245#issuecomment-271289469. Heavily WIP. Happy to collaborate!