Strange problem with AU1, inheritance, DialogService and dumber

Greetings.

My setup is Aurelia 1 + TypeScript + dumber.

I am encountering a strange problem with this (simplified) setup which involves class inheritance and DialogService.

In short:

  • base component which opens a dialog using its class as the viewModel
  • two derived components
  • page which requires both components

At runtime, the page breaks with the following error:

Edge:

TypeError: Class extends value undefined is not a constructor or null

Firefox:

TypeError: class heritage inspectionAnswerListBase_1.InspectionAnswerListBase is not an object or null

What happens is that the first component works correctly, but in the second one the base class is undefined. Note that this is not dependent on the actual components but only in their order (the first works, the second breaks).
However, specifying the viewModel for the dialog as a module path (string) instead of an import DOES work.

Out tsconfig.json is:

{
  "compileOnSave": false,
  "compilerOptions": {
    "rootDir": "src/",
    "sourceMap": true,
    "target": "ES2017",
    "module": "AMD",
    "declaration": false,
    "removeComments": true,
    "importHelpers": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "noEmitOnError": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "alwaysStrict": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitAny": false,
    "noImplicitOverride": true
  },
  "exclude": [
    "node_modules",
    "obj",
    "bin"
  ]
}

I do not seem to be able to pinpoint the exact source of the problem, I suspect some odd edge case.
I am not even sure whether I should expect a solution, but maybe something here rings a bell for someone.

1 Like

you probably got a circular dependency somewhere somehow, causing the Base to be evaluated too early before the module itself is ready. Hence it’s returning undefined because of the way Webpack bundles the modules. Check your dependency graph or maybe simplify the base.ts file.

Thank you from your attention. Also tagging @huochunpeng in case this has something to do with dumber.
I had thought about circular dependencies but I could not find any (I plan to check this again more thouroughly later).

I have added to each file a simple console.log after the various imports and they seem to be loaded in the wrong order:

required: Derived2
required: SomeComponent
required: Base
required: Derived1

I would expect Derived2 to be loaded after SomeComponent and Base, which is what happens if I remove the reference to SomeComponent.

required: Base
required: Derived1
required: Derived2

There seems to be something odd with the dialog viewmodel but I have yet to discover what.

The first thing you can debug is to use webpack with the same code, to see if you can duplicate same issue. If webpack has no issue, then it’s probably a bug in dumber that I can help to look into.

“Using Webpack with the same code” does not really seem straightforward; I will have to look more into it.
For now more testing showed the problem might be related to SomeComponent and Base being in different bundles; there might be some hidden circular dependency somewhere.

Circular deps can only be supported within one bundle. You can update your dumber config, just comment out code splitting, to see if it works inside one bundle.

I don’t remember whether dumber log a warning when circular deps are detected, you can have a look of your console log when bundling.

I can confirm that by not splitting code the problem goes away. I will have to check for circular dependencies more thoroughly.
If indeed it is due to circular dependencies, do you have any idea on why the problem happens ONLY if the component is required more than one time?

My guess is your SomeComponent has a dep (or deep dep through another import) on Base.

Another suggestion, instead of using abstract base class, you can use DI + service pattern to share common functionality.

I think current trend is to avoid using inheritance in OOP.

class CommonService {
  openDialog() {...}
}

class Comp1 {
  constructor(commonService: CommonService) {}
  hitButton() {
    this.commonService.openDialog().then(...)
  }
}
2 Likes

An update on this since it happened again.
I suspect some problem with DefaultLoader and dumber, something like this: if two components with the same parent class are required very close together, in one of them the parent module has not been loaded yet and the instantiation fails.

Modules are loaded in parallel:

I have tried to replace this parallel approach with a serial one:

    DefaultLoader.prototype.loadAllModules = async function loadAllModules(ids: string[]) {
        const imports = [];
        for (const id of ids)
            imports.push(await this.loadModule(id));
        return imports;
    };

and in my new case the error no longer occurs.
I have not been able to find the code which prompted me to open this thread so I cannot check if it would have been solved too.

1 Like

Since you have code split on, if the dependencies are across two bundle files, the AMD loader has to asynchronously load the additional bundle file.
if you turn off code split, all loadings are synchronous, so there is no such issue.
It must be a racing condition in my AMD loader (dumber-module-loader), let me reproduce it in unit test and fix it.

Do you have a piece of code to reproduce the issue? I cannot find a combination locally to reproduce the loading issue.
dumber-module-loader does have some code to avoid duplicated bundle loading, it handles my test cases without any problem. There must be a dependency condition I have not thought of.