TypeScript inheritance and circular dependencies in SystemJS

感情迁移 提交于 2019-12-10 15:56:42

问题


I'm using TypeScript with --module system (SystemJS) in a very large project. SystemJS supports cyclic dependencies, and most of the time it works fine. However, when TypeScript inheritance gets involved, things begin to break.

For example, if a class A depends on class B, and class B inherits from class A, then if class A gets loaded first:

  1. It will pause class A's resolution and will try to load the class B dependency
  2. class B will think its dependencies are resolved, since class A has been touched.
  3. class B's inheritance will fail to resolve, because class A is still undefined.

Most of the "solutions" I can find on the web to circular dependencies with module loaders are either:

  • Change your design / combine classes into a single module
  • CommonJS and non-TypeScript specific workarounds

I feel like there are valid justifications for circular designs, and combining classes into giant files is not always desirable, so please consider these workarounds to be off topic for the question that I am asking.

Are there any solutions to the actual problem?


回答1:


Changing your design is the most favourable solution. A class should not depend on its subclasses. If you use them in a factory or so, that is a separate concern and should go in a separate class/function/module.

Are there any solutions to the actual problem?

As you said, the problem occurs only when module A is loaded first. The solution is to prevent that, and write an extra module that acts as a proxy to A and all its subclasses while importing them in the correct order.




回答2:


In this case I suggest you remove the dependency of A -> B by creating a separate interface I. Both A and B need to know I, and B needs to implement it.

During your load process B must tell A where to find a constructor or factory for I (implemented by B). This will leave you with those dependencies:

A -> I
B -> I
B -> A

Interface I could look like this:

interface I {
  bFoo(): void;
}

export default I;

Class A could look like this:

import I from "./i";

class A {
  private static __ICtor : new() => I;
  public static setIConstructor(ctor: new() => I) {
    A.__ICtor = ctor;
  }

  private __atSomePoint() : I {
    return new A.__ICtor();
  }
}

export default A;

And finally class B:

import I from "./i";
import A from "./a";

class B extends A implements I {
  public bFoo() {}
}

A.setIConstructor(B);

IMHO this would solve your cyclic dependency, even if it is too late by now.




回答3:


There's a neat way to solve this issue by using the Babel transform plugin here: https://github.com/zertosh/babel-plugin-transform-inline-imports-commonjs

What it does is it converts the at-start-of-file module imports into inline requires that only actually import/require the other modules just before they're used.

In most cases, this solves the problem automatically, as by the time any class-using code actually runs, the modules have all completed their exports.


Note that by default the plugin above applies to all imports in your project, turning them all into inline requires. A more serious issue, however, is that I couldn't find a built-in way to make it work with relative-path imports/requires.

I fixed both these issues in my fork of the project here: https://github.com/Venryx/babel-plugin-transform-inline-imports-commonjs

I made a pull request for these changes, but it's awaiting review atm.



来源:https://stackoverflow.com/questions/37306749/typescript-inheritance-and-circular-dependencies-in-systemjs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!