question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Recursive Node.js require

See original GitHub issue

Recursive 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 required 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:open
  • Created 7 years ago
  • Reactions:3
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
calebmercommented, Jan 9, 2017

Ok but inherent problems aside I don’t see anything that really blocks changing the emit behavior from this:

"use strict";
var foo_1 = require("./foo");
exports.a = foo_1.a;
var bar_1 = require("./bar");
exports.b = bar_1.b;

To this:

'use strict';

var foo_1 = require('./foo');
Object.defineProperty(exports, 'a', {
  enumerable: true,
  get: () => foo_1.a,
});

var bar_1 = require('./bar');
Object.defineProperty(exports, 'b', {
  enumerable: true,
  get: () => bar_1.b,
});

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:

var foo_1 = require('./foo');
Object.defineProperty(exports, 'a', {
  configurable: true,
  enumerable: true,
  get: () => foo_1.a,
  set: value => { foo_1.a = value; },
});

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 😖

1reaction
squidfunkcommented, Jun 1, 2019

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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Node.js: recursive require and exports - Stack Overflow
Any help? This situation is not so tricky: I would like to group my scripts in a folder with a unique entry script,...
Read more >
CommonJS modules | Node.js v19.3.0 Documentation
CommonJS modules are the original way to package JavaScript code for Node.js. Node.js also supports the ECMAScript modules standard used by browsers and ......
Read more >
Node.js - How to load modules recursively from directory
Quick how-to load files in node.js as modules. index.js: require('fs').readdirSync(__dirname).forEach(function (file) { /* If its the current file ignore it ...
Read more >
JavaScript Recursive Function By Examples
This tutorial shows you how to use the recursion technique to develop a JavaScript ... Suppose that you need to develop a function...
Read more >
Recursively List All the Files in a Directory Using Node.js
To do this, we need to create a recursive function that can call itself when dealing with sub-directories. And we also need the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found