-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
TypeScript Version: 4.1.0-dev.20201022
Search Terms: emitDecoratorMetadata, decorators, design:type, type, resolver, reflection
Code:
Suppose we are doing Node.js code and have a plain old circular dependency:
classOne.ts
import { property } from './utils';
import ClassTwo from './classTwo';
export default class ClassOne {
@property
property: ClassTwo;
}classTwo.ts
import { property } from './utils';
import ClassOne from './classOne';
export default class ClassTwo {
@property
property: ClassOne;
}and suppose we want to bring a bit of reflection in our code:
index.ts
import 'reflect-metadata';
import { getDesignType } from './utils';
import ClassOne from './classOne';
import ClassTwo from './classTwo';
console.log(`Hello World! ${ClassOne.name} and ${ClassTwo.name};)`);
console.log(`--`);
console.log(`${ClassOne.name} property is of type ${getDesignType(ClassOne, 'property')}`);
console.log(`${ClassTwo.name} property is of type ${getDesignType(ClassTwo, 'property')}`);utils.ts
/**
* Use typescript emited metadata to get the type
*
* @param ctor
* @param key
*/
export const getDesignType = (ctor: Function, key: string) => {
const type = Reflect.getMetadata('design:type', ctor.prototype, key);
return type?.name;
}
/**
* Dummy decorator
*
* @param target
* @param key
*/
export const property = (target: any, key: string) => { }Expected behavior:
foo@bar:~$ node ./dist
Hello World! ClassOne and ClassTwo;)
--
ClassOne property is of type ClassTwo
ClassTwo property is of type ClassOneActual behavior:
foo@bar:~$ node ./dist
Hello World! ClassOne and ClassTwo;)
--
ClassOne property is of type ClassTwo
ClassTwo property is of type undefinedExplanation:
Well it's not much a surprise, we are running into a circular import issue:
- index.ts is importing ClassOne
- ClassOne is importing ClassTwo
- ClassTwo is importing ClassOne
- Since ClassOne is being read, return the cached value of ClassOne
__metadata("design:type", classOne_1.default)is evaluatedclassOne_1.defaultis evaluated to undefined- ...
Workaround:
There is a simple workaround and it's already used by many libraries but unfortunately not by typescript. The idea is not to emit the type but to emit a type resolver ie. instead of emitting ClassFoo one should emit () => ClassFoo. This way we can evaluate the type resolver in index.ts at a time where ClassOne import is already done.
Playground Link:
https://github.com/paeolo/typescript-emit-resolver
Related Issues:
This issue is the root of many issues. Solving this will enable a much better code where reflection is used by not introducing the type resolver as a redondancy.
Please note that trying to solve the circular import is not a good solution. Sometimes circular import are inherents and it's very hard to both focus on code and trying to solve every "circular import" related problems. tslib would benefit a lot from being more resilient to such issues.