microsoft / tsyringe

Lightweight dependency injection container for JavaScript/TypeScript
MIT License
5.18k stars 172 forks source link

Webpack/Babel can't handle '@' in constructor #220

Open ihl396 opened 1 year ago

ihl396 commented 1 year ago

Describe the bug

Attempting to generate a react build via webpack and babel-loader fails when there's a parameter decorator in the constructor - even though I included the 'babel-plugin-transform-typescript-metadata' plugin. The workaround is obviously to not include the inject parameter decorator in the constructor and DI manually (which works so I'm unblocked), but this seems like a bug that should be tracked nonetheless?

I've originally referred to this closed issue: https://github.com/microsoft/tsyringe/issues/142 which still did not resolve my issue.

Generated output:

> create-react-app@0.1.0 build
> react-scripts build

Creating an optimized production build...
Failed to compile.

Module parse failed: Unexpected character '@' (1:422)
File was processed with these loaders:
 * ./node_modules/babel-loader/lib/index.js
 * ./node_modules/source-map-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
> import _createClass from"D:/projects/app/node_modules/@babel/runtime/helpers/esm/createClass.js";
import _classCallCheck from"D:/projects/app/node_modules/@babel/runtime/helpers/esm/classCallCheck.js";
var _dec,_class;import"reflect-metadata";import{inject,singleton}from'tsyringe';
export var App=(_dec=singleton(),_dec(_class=
/*#__PURE__*/_createClass(function App(@inject("Store")storeService){_classCallCheck(this,App);this.storeService=void 0;}

To Reproduce

Generate a build by invoking react-scripts build with the configured webpack.config.js and .babelrc

import "reflect-metadata";
import { container, inject, singleton } from 'tsyringe'

@singleton()
export class App {
  storeService: typeof store
  constructor(@inject("Store") storeService: typeof store) {}
}

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = ({ mode } = { mode: "production" }) => {
  console.log(`mode is: ${mode}`);

  return {
    mode,
    entry: "./src/index.tsx",
    devtool: 'inline-source-map',
    output: {
      publicPath: "/",
      path: path.resolve(__dirname, "dist"),
      filename: "bundle.js"
    },
    devServer: {
      static: [
        { directory: path.join(__dirname, 'public') },
        { directory: path.join(__dirname, 'src/common') }
      ],
      compress: true,
      hot: true,
      watchFiles: {
        paths: ['src/common/**/*.ts'],
        options: {
          usePolling: false
        }
      },
      // open: true,
      port: 3030
    },
    resolve: {
      extensions: [ '.tsx', '.ts', '.js' ],
    },
    module: {
        rules: [
          {
            test: /\.css$/i,
            exclude: /node_modules/,
            use: ["style-loader", "css-loader"],
          },
          {
            test: /\.svg$/,
            exclude: /node_modules/,
            loader: "svg-url-loader",
          },
          {
            test: /\.jpe?g|png|ico$/,
            exclude: /node_modules/,
            use: ["url-loader", "file-loader"]
          },
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            loader: "babel-loader",
            options: {
              plugins: ['react-refresh/babel'],
            },
          },
          {
            test: /\.(ts|tsx)$/,
            use: [
              {
                loader: 'ts-loader',
                options: {
                  transpileOnly: true
                }
              }
            ],
            exclude: /node_modules/,
          },
        ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "./public/index.html",
        manifest: "./public/manifest.json",
        favicon: "./public/favicon.ico"
      }),
    ]
  }
};

tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "jsx": "react-jsx",
    "sourceMap": true,
    "outDir": "./dist/"
  },
}

.babelrc

{
    "presets": [
        "@babel/preset-react",
        [
            "@babel/preset-env",
            {
                "targets": {
                    "browsers": "last 2 versions"
                },
                "modules": false,
                "loose": false
            }
        ]
    ],
    "plugins": [
      "transform-class-properties",
      "babel-plugin-transform-typescript-metadata",
    ],
    "env": {
        "test": {
            "plugins": [
                "transform-es2015-modules-commonjs"
            ]
        }
    }
}

Expected behavior

Be able to generate a webpack build using parameter decorators. Version: "tsyringe": "^4.7.0", "webpack": "^5.74.0", "@babel/core": "^7.19.3", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.20.13", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "babel-loader": "^8.2.5", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "babel-plugin-transform-typescript-metadata": "^0.3.2",