thiagobustamante / typescript-ioc

A Lightweight annotation-based dependency injection container for typescript.
MIT License
524 stars 64 forks source link

Dependency injection does not work for instances provided by a linked library (npm link) #62

Closed jepetko closed 4 years ago

jepetko commented 4 years ago

DI does not work for libraries which are linked locally via npm link some-lib.

classes provided by some-lib:

@Singleton export class A { constructor() { console.info('A created'); }

method() {
    console.info('a: method');
}

}


* class B:
```typescript
import {Inject, Singleton} from 'typescript-ioc';
import {A} from './a';

@Singleton
export class B {
    constructor(@Inject private a: A) {
        console.info('B created, will now call this.a.method()');
        this.a.method();
    }
}

usage in some-app whereas the library is used as a linked library (npm link some-lib):

IMPORTANT: note that the behavior is the same no matter whether the alias is used or not (also see https://github.com/jepetko/some-app/commit/d658f49542100b009ffd5aeae13c332a63958dc6#diff-aaae91aceb351151eeeb6a1e04817a9c which does not contain any alias)

import alias from 'module-alias';
import path from 'path';
import {Container, Inject} from 'typescript-ioc';

// handle alias properly for the local development (ts-node-dev) and production (node)
const extension = path.extname(process.mainModule.filename);
alias.addAlias('@awesome-lib', path.join(process.cwd(), 'node_modules', 'some-lib', 'dist'));
alias.addAlias('@awesome/app', (extension === '.ts') ? __dirname : `${process.cwd()}/build`);

import {B} from '@awesome-lib/b';

export class Main {

    constructor(@Inject private b: B) {
        console.info('Main injected b: ', this.b);
    }
}

Container.get(Main);

Expected result for npm start (works when the lib is installed via npm i or copied into node_modules):

A created
B created, will now call this.a.method()
a: method
Main injected b:  B {
  a:
   A { __BuildContext: ContainerBuildContext { context: Map {} } },
  __BuildContext: ContainerBuildContext { context: Map {} } }

Actual result for npm start (does not work when the lib is installed via npm link some-lib):

B created, will now call this.a.method()
C:\data\workspaces\dist\b.js:21
        this.a.method();
               ^

TypeError: Cannot read property 'method' of undefined
    at new B (C:\data\workspaces\some-lib\dist\b.js:21:16)
    at factory (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:21:63)
    at iocFactory (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:36:30)
    at LocalScope.resolve (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\scopes.js:10:16)
    at IoCBindConfig.getInstance (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:71:30)
    at IoCBindConfig.get [as instanceFactory] (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container.js:46:23)
    at paramTypes.map.paramType (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:88:29)
    at Array.map (<anonymous>)
    at IoCBindConfig.getParameters (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:84:36)
    at factory (C:\data\workspaces\some-app\node_modules\typescript-ioc\dist\container\container-binding-config.js:19:37)

Sources

jepetko commented 4 years ago

This issue is related to the way how npm resolves the modules. In this case, decorators placed in the lib (for B and A) are referring stuff from some-lib\node_modules\typescript-ioc. However, some-app is using node_modules\typescript-ioc. Those two DI containers don't know anything about each other and thus A cannot be injected into B.

One possible solution for a reasonable local development would be to use some-lib linked, however, without node_modules installed. Then, --preserve-symlinks would use the local typescript-ioc dependency. The caveat is that modules must not be installed in some-lib.

Any better ideas?

jepetko commented 4 years ago

So here are the options:

Option A:

  1. in some-lib: put typescript-ioc as devDependency (and ambientDependency)
  2. link some-lib with npm link --only=production

the drawback is that node_modules must not contained development dependencies which is not feasible as you need devDependencies for further development of the library.

Option B:

provide a script which does the following:

"my-link": "mv node_modules node_modules_backup && npm link",
"my-unlink": "npm unlink && mv node_modules_backup node_modules"