简体   繁体   中英

Inversify cannot inject class with dependencies

I started a new typescript project based an old project of mime (which started as plain javascript) and I can't get any non-trivial injection working. I know about nothing about babel configuration (because of staring with create-react-app). I tried the recommended compilerOptions and it didn't work either..... my setup may be a mess, but that's how it evolved.

Feel free to get the whole project from github (get it and run npm install && npm start ) or continue reading.

import 'reflect-metadata';
import { Container } from 'inversify';
import React from 'react';
import ReactDOM from 'react-dom';
import { injectable } from 'inversify';

@injectable()
export class Simple {
}

@injectable()
export class Composed {
    simple: Simple;

    // ERROR: Module parse failed: Unexpected character '@'
    // constructor(@inject simple: Simple) {
    //  this.simple = simple;
    // }

    // Error: Missing required @inject or @multiInject annotation in: argument 0 in class Composed.
    constructor(simple: Simple) {
        this.simple = simple;
    }
}

const container = new Container();
container.bind(Simple).toSelf();
container.bind(Composed).toSelf();
console.log(container.get(Composed));

function Main() {
    return <div>
        {JSON.stringify(container.get(Composed))}
    </div>;
}

ReactDOM.render(<Main/>, document.getElementById('root'));

package.json

{
    "name": "inversify-problem",
    "version": "0.0.1",
    "description": "",
    "private": true,
    "dependencies": {
        "@material-ui/core": "^4.9.8",
        "@material-ui/icons": "^4.9.1",
        "array.prototype.flatmap": "^1.2.3",
        "customize-cra": "^0.9.1",
        "deep-equal": "^2.0.1",
        "eslint": "^6.8.0",
        "inversify": "^5.0.1",
        "json-stable-stringify": "^1.0.1",
        "mobx": "^5.15.4",
        "mobx-decorators": "^6.0.1",
        "mobx-react": "^6.1.8",
        "mobx-state-tree": "^3.15.0",
        "notistack": "^0.9.9",
        "react": "^16.13.1",
        "react-app-polyfill": "^1.0.6",
        "react-app-rewire-mobx": "^1.0.9",
        "react-app-rewired": "^2.1.5",
        "react-dom": "^16.13.1",
        "reflect-metadata": "^0.1.13",
        "resize-observer-polyfill": "^1.5.1",
        "shallowequal": "^1.1.0",
        "styled-components": "^5.0.1",
        "ts-enum-util": "^4.0.1"
    },
    "devDependencies": {
        "babel-plugin-import": "^1.13.0",
        "babel-plugin-styled-components": "^1.10.7",
        "@types/classnames": "^2.2.10",
        "@types/deep-equal": "^1.0.1",
        "@types/json-stable-stringify": "^1.0.32",
        "@types/jest": "^25.1.4",
        "@types/node": "^13.9.8",
        "@types/react": "^16.9.29",
        "@types/react-dom": "^16.9.5",
        "eslint-plugin-unused-imports": "^0.1.2",
        "tslint-etc": "^1.10.1",
        "react-scripts": "^3.4.1",
        "typescript": "^3.8.3"
    },
    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test",
        "eslint": "eslint --fix $(find src -name '*.ts' -o  -name '*.tsx')",
        "eject": "react-scripts eject"
    },
    "eslintConfig": {
        "extends": "react-app",
        "parserOptions": {
            "ecmaFeatures": {
                "legacyDecorators": true
            }
        }
    },
    "browserslist": [
        ">0.2%",
        "not dead",
        "not ie <= 11",
        "not op_mini all"
    ]
}

tsconfig.json

{
    "compilerOptions": {
        "target": "ES5",
        "sourceMap": true,
        "downlevelIteration": true,
        "importHelpers": true,
        "lib": [
            "dom",
            "dom.iterable",
            "esnext",
            "es6",
            "webworker",
            "es2015.collection",
            "es2015.iterable",
            "es2019"
        ],
        "types": [
            "node",
            "reflect-metadata"
        ],
        "allowJs": false,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "strictNullChecks": true,
        "forceConsistentCasingInFileNames": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "noImplicitAny": true,
        "noEmitOnError": false,
        "incremental": false,
        "jsx": "preserve",
        "noEmit": true
    },
    "include": [
        "src/index.tsx",
        "src/mg/extras.d.ts"
    ]
}

config-overrides.js

const {
    override,
} = require("customize-cra");

module.exports = override(
);

Debugging

I tried to debug the problem, but found not much out. There are no metadata, no attempt is made to parse Function.prototype.toString() like in this answer . There's something wrong with my configuration, but that thing is pretty opaque to me.

You are struggling because the react babel config does not allow decorators, so what you can do is:

$ npm run eject
$ npm install react-scripts # if you use an older npm installation then you need to add --save
$ npm install -D @babel/plugin-proposal-decorators 
$ npm install -D babel-plugin-parameter-decorator

Your package.json is now expanded with a lot of options, find the babel section and change it as follows:

  "babel": {
    "plugins": [
      [
        ["@babel/plugin-proposal-decorators", { "legacy": true }],
        "babel-plugin-parameter-decorator"
      ]
    ],
    "presets": [
      // ...
      ["@babel/preset-typescript", { "onlyRemoveTypeImports": true }]
    ]
  }

now you can at least use field injection as follows:

// ...
import { inject, injectable } from 'inversify';
// ...

@injectable()
class Composed {
    private readonly simple: Simple;

    public constructor(@inject(Simple) simple: Simple) {
        this.simple = simple;
    }
}

//...

The solution is based on this:

For more info on why decorators isn't supported:

I had the same solution as posted but instead of using package.json , I used config-overrides.js :

const {
  override,
  addDecoratorsLegacy,
  addBabelPlugin,
} = require("customize-cra");

module.exports = override(
  addDecoratorsLegacy(),
  addBabelPlugin("babel-plugin-parameter-decorator")
);

To be honest, I didn't like this solution so I didn't post it...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM