Monday, November 13, 2017

Node.js, circularly dependent modules and intellisense in VS Code

Circularily dependand modules in Node.js is something most people recommend you should definitely avoid. Let's start with an example of two classes that mutually require each other:
// a.js
var B = require('./b');

class A {
    createB() {
        return new B();
    }
}
    
module.exports = A;

// b.js
var A = require('./a');

class B {
    createA() {
        return new A();
    }    
}

module.exports = B;

// app.js
var A = require('./a');
var B = require('./b');

var a = new B().createA();
var b = new A().createB();

console.log(a);
console.log(b);
Although this looks correct and intellisense works correctly, this of course doesn't work because of the circular dependency.
And while there are multiple resources that cover the topic (e.g. [1] or [2]) I'd like to emphasize that VS Code does a nice job in inferring of what actual type of the returned value from the method is, regardless of what approach to the issue you take.

Let's check the first approach, where you inline the reference

// b.js
class B {
    createA() {
        var A = require('./a');
        return new A();
    }    
}

module.exports = B;
Note that the VS Code still correctly infers the return type of the method:
The second approach involves moving requires to the end of both modules
// a.js
class A {
    createB() {
        return new B();
    }
}
    
module.exports = A;

var B = require('./b');

// b.js
class B {
    createA() {
        return new A();
    }    
}

module.exports = B;

var A = require('./a');
The type inference still works.

The last fact worth mentioning is that VS Code respects the JSDoc's @type annotation, whenever there type can't be inferred automatically, you can help it.

Take a look at VS Code not able to infer the actual type of a variable

var a = new B().createA();
var b = new A().createB();

var _unknown;
and how a hint helps it
var a = new B().createA();
var b = new A().createB();

/** @type {A} */
var _unknown;