nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.18k stars 2.3k forks source link

React class component babel error: "Missing class properties transform" #1779

Closed jmsv closed 5 years ago

jmsv commented 5 years ago

I've jumped into using Nx today and so far I love it and intend to keep using it! Just came across the following problem and it looks like it could be an issue with how Nx sets up React apps?

Expected Behavior

Can use class properties/arrow functions in React components

Current Behavior

Defining anything in a React component class (extends React.Component) fails with:

ERROR in ./app/app.tsx
Module build failed (from /d/path/node_modules/babel-loader/lib/index.js):
SyntaxError: /d/path/apps/appname/src/app/app.tsx: Missing class properties transform.

Failure Information

Looks like the same as this issue: babel/babel#2729

This comment explains that it's something to do with the order of the babel presets: https://github.com/babel/babel/issues/2729#issuecomment-245401360

Steps to Reproduce

npx create-nx-workspace@latest repro-thing --preset=react

Use the following apps/repro-thing/src/app/app.tsx (a simple counter app):

import React from 'react';
import './app.scss';

type Props = {};

type State = {
  count: number;
};

export class App extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  updateCount = (amount: number) => {
    this.setState({ count: this.state.count + amount });
  };

  render() {
    return (
      <div className="app">
        <h1>counter</h1>

        <div>
          <button onClick={() => this.updateCount(-1)}>-</button>

          {this.state.count}

          <button onClick={() => this.updateCount(1)}>+</button>
        </div>
      </div>
    );
  }
}

export default App;

Run npm start and wait for "Failed to compile."

I've tried the same code in a React app generated with npx create-react-app thing --typescript and this example works as expected.

Context

Dependencies/Versions

  "dependencies": {
    "document-register-element": "1.13.1",
    "react": "16.9.0",
    "react-dom": "16.9.0",
    "@nestjs/common": "^6.2.4",
    "@nestjs/core": "^6.2.4",
    "@nestjs/platform-express": "^6.2.4",
    "reflect-metadata": "^0.1.12"
  },
  "devDependencies": {
    "@babel/core": "7.5.5",
    "@babel/plugin-proposal-decorators": "7.4.4",
    "@babel/preset-env": "7.5.5",
    "@babel/preset-react": "7.0.0",
    "@babel/preset-typescript": "7.3.3",
    "@nestjs/schematics": "^6.3.0",
    "@nestjs/testing": "^6.2.4",
    "@nrwl/cypress": "8.4.13",
    "@nrwl/eslint-plugin-nx": "8.4.13",
    "@nrwl/jest": "8.4.13",
    "@nrwl/nest": "^8.4.13",
    "@nrwl/node": "8.4.13",
    "@nrwl/react": "8.4.13",
    "@nrwl/web": "8.4.13",
    "@nrwl/workspace": "8.4.13",
    "@testing-library/react": "8.0.5",
    "@types/jest": "24.0.9",
    "@types/node": "~8.9.4",
    "@types/react": "16.9.1",
    "@types/react-dom": "16.8.5",
    "@typescript-eslint/eslint-plugin": "2.0.0-alpha.4",
    "@typescript-eslint/parser": "2.0.0-alpha.4",
    "babel-loader": "8.0.6",
    "babel-plugin-macros": "2.6.1",
    "concurrently": "^4.1.2",
    "core-js": "3.1.4",
    "cypress": "3.4.1",
    "dotenv": "6.2.0",
    "eslint": "6.1.0",
    "eslint-config-prettier": "6.0.0",
    "eslint-plugin-import": "2.18.2",
    "eslint-plugin-jsx-a11y": "6.2.3",
    "eslint-plugin-react": "7.14.3",
    "eslint-plugin-react-hooks": "1.6.1",
    "jest": "24.1.0",
    "prettier": "1.16.4",
    "regenerator-runtime": "0.13.3",
    "ts-jest": "24.0.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~3.4.5"
  }

Failure Logs

logs

Other

Changing the function to a normal class function and then binding this function to the scope rather than defining an arrow function works, e.g.:

  constructor(props) {
    super(props);
    this.state = { count: 1 };

    this.updateCount = this.updateCount.bind(this);
  }

  updateCount(amount: number) {
    this.setState({ count: this.state.count + amount });
  };

but this means you have to bind every method, which I'd rather avoid.

This error also occurs for other value types, e.g. numbers:

export class App extends React.Component<Props, State> {
  thing = 1

  constructor(props) {
    ...

Gives the following command-line error:

[ui] ERROR in ./app/app.tsx
[ui] Module build failed (from /d/path/node_modules/babel-loader/lib/index.js):
[ui] SyntaxError: /d/path/apps/appname/src/app/app.tsx: Missing class properties transform.
[ui]   10 |
[ui]   11 | export class App extends React.Component<Props, State> {
[ui] > 12 |   thing = 1
[ui]      |   ^
[ui]   13 | 
[ui]   14 |   constructor(props) {
[ui]   15 |     super(props);
vsavkin commented 5 years ago

@jmsv thanks for checking out Nx and submitting the issue. We will look into it and hopefully land a fix in the next release.

nikita-yaroshevich commented 5 years ago

As for now, I found the following solution:

  1. npm install --save-dev babel-plugin-transform-class-properties
  2. create tools/build/@babel-react/babel.js
"use strict";
// Object.defineProperty(exports, "__esModule", { value: true });
const getBabelWebpackConfig = require('@nrwl/react/plugins/babel');
module.exports = function (config) {
    const cfg = getBabelWebpackConfig(config);
    var idx = cfg.module.rules.findIndex(function (r) {
        return r.use && r.use.loader === 'babel-loader';
    });
    var rule = cfg.module.rules[idx];
    if (rule) {
        rule.use.options.plugins = [
            require('babel-plugin-macros'),
            [require('@babel/plugin-proposal-decorators').default, {
                legacy: true
            }], // enable TS decorators\annotations
            require('babel-plugin-transform-class-properties') // hotfix error: "Missing class properties transform"
        ];
        cfg.module.rules.splice(idx, 1, rule);
    } else {
        console.warn('!!!!!!!!!  ATTENTION  !!!!!!!!');
        console.warn('* Invalid NRWL build process override *');
        console.warn('* Please take a look at ' + __filename + ' *');
        console.warn('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
    }
    return cfg;
};
  1. into workspace.json, edit projects.<appname>.architect.build.options.webpackConfig as ... "webpackConfig": "./tools/build/@babel-react/babel.js" ...
  2. profit
jmsv commented 5 years ago

thanks for checking out Nx and submitting the issue. We will look into it and hopefully land a fix in the next release.

awesome, thanks @vsavkin!


@nikita-yaroshevich that works perfectly for now, thanks for the quick response! now to work out step 4 😄

jaysoo commented 5 years ago

@jmsv This will be fixed in the next release (soon!), and then you won't need the custom webpack config. :)

jaysoo commented 5 years ago

Specifically in this PR #1792.

nikita-yaroshevich commented 5 years ago

@jaysoo Maybe you will add TS decorators support for React Apps? Or it out of the scope?

jmsv commented 5 years ago

@jaysoo awesome! thanks guys :fire:

jaysoo commented 5 years ago

@nikita-yaroshevich We can add the support for decorators.

jaysoo commented 5 years ago

@nikita-yaroshevich decorators will be supported in the next release. https://github.com/nrwl/nx/pull/1798

jhlabs commented 4 years ago

@jaysoo I am using NX with NextJS and several react libraries that are run with Storybook. The exact same error as mentioned above appears when I run Storybook, but not for NextJS. Could it be that the fix did not apply to Storybook installs? I tried to fix it with the hotfix above and custom .babelrc configurations, but could not get it to work. Thanks for your help!

countravioli commented 4 years ago

@jhlabs were you able to resolve this? I'm running into the same thing. LibB storybook imports a Component from LibA

jhlabs commented 4 years ago

@countravioli unfortunately not. I rewrote my components to functional ones to avoid this issue, but that will not be a solution for most codebases 🙃

kimon89 commented 4 years ago

I am facing the same issue with @jhlabs. Storybook breaks with Missing class properties transform.. Kind of blocked with this one as we can't refactor all our components to functional components.

maxim-kha commented 4 years ago

@kimon89, @jhlabs Guys try something like this webpack.config.js

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const rootWebpackConfig = require('../../../.storybook/webpack.config');
// Export a function. Accept the base config as the only param.
module.exports = async ({ config, mode }) => {
    config = await rootWebpackConfig({ config, mode });

    const tsPaths = new TsconfigPathsPlugin({
        configFile: './tsconfig.json',
       });

      config.resolve.plugins
        ? config.resolve.plugins.push(tsPaths)
        : (config.resolve.plugins = [tsPaths]);

    config.resolve.extensions.push('.tsx');
    config.resolve.extensions.push('.ts');
    config.module.rules.push({
        test: /\.(ts|tsx)$/,
        loader: require.resolve('babel-loader'),
        options: {
            presets: [
                '@babel/preset-env',
                '@babel/preset-react',
                '@babel/preset-typescript',                                           
            ],
            "plugins": [
                ["@babel/plugin-proposal-decorators", { "legacy": true }],
                ["@babel/plugin-proposal-class-properties", { "loose" : true }]
              ],          
        },

    });
    // This is needed for "@storybook/addon-storysource.
    // Without this story source code won't show in storybook.
    config.module.rules.push({
        test: /\.stories\.(ts|tsx)?$/,        
        loaders: [
            require.resolve('@storybook/source-loader'),            
          ],
        enforce: 'pre',
    });

    return config;
};

adding

   "plugins": [
                ["@babel/plugin-proposal-decorators", { "legacy": true }],
                ["@babel/plugin-proposal-class-properties", { "loose" : true }]
              ], 

solve problem for me.

github-actions[bot] commented 1 year ago

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.