vazco / uniforms

A React library for building forms from any schema.
https://uniforms.tools
MIT License
1.96k stars 244 forks source link

meteor 1.7.0.1 - TypeError: Class constructor _class cannot be invoked without 'new' #433

Closed macrozone closed 5 years ago

macrozone commented 6 years ago

this line fails in meteor 1.7.0.1:

import AutoFormBase from 'uniforms/AutoForm';

const Auto = parent => class extends AutoFormBase.Auto(parent) {
  static Auto = Auto;
};

any idea?

full error:

modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:98346 Uncaught (in promise) TypeError: Class constructor _class cannot be invoked without 'new'
    at new _class (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:98346)
    at new _class (AutoForm.js:9)
    at modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80870
    at measureLifeCyclePerf (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80651)
    at ReactCompositeComponentWrapper._constructComponentWithoutOwner (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80869)
    at ReactCompositeComponentWrapper._constructComponent (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80860)
    at ReactCompositeComponentWrapper.mountComponent (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80763)
    at Object.mountComponent (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:73688)
    at ReactCompositeComponentWrapper.performInitialMount (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80946)
    at ReactCompositeComponentWrapper.mountComponent (modules.js?hash=441a59febac0d39753b5eebcca0bbdcb08b83a80:80833)
radekmie commented 6 years ago

It's probably related to different versions of Babel used by uniforms and Meteor. While I'm not sure, I'd ask for a reproduction too (we can track it in the Meteor issue if you want).

macrozone commented 6 years ago

@radekmie does your example project work under meteor 1.7? did not have time to setup a reproduction yet

radekmie commented 6 years ago

Haven't tried it yet. I'll give it a shot later this week.

radekmie commented 6 years ago

Unluckily, I've made two attempts and failed twice. Any progress on your site?

EDIT: Yep, I've tried updating Babel too (7.0.0-beta.49).

macrozone commented 6 years ago

@radekmie i tried to update to 1.25.0-rc.3, but i still get the same error. Do i need to bump something else as well?

Edit: i also bumped all babel stuff to beta 51 without success

radekmie commented 6 years ago

I got it working in the demo app. If you could provide a reproduction, please do - I’ll reopen it then.

macrozone commented 6 years ago

I had no success yet.

In your .babelrc you have

[
    "@babel/preset-env",
    {
      "useBuiltIns": "usage"
    }
  ],

i am not sure if this messes with meteor 1.7's legacy and modern browser support?

maybe @benjamn can shed some light on that

macrozone commented 6 years ago

@radekmie I have

import AutoFormBase from 'uniforms/AutoForm';

const Auto = parent =>
  class extends AutoFormBase.Auto(parent) {
    static Auto = Auto;
  };

const AutoForm = Auto(ValidatedQuickForm);

in my meteor app, because I have a custom AutoForm component

Maybe that's the problem? in the demo-app this is not the case

Edit: Also @benjamn 's comment here https://github.com/meteor/meteor/issues/9934#issuecomment-394356011 indicates, that its because AutoFormBase.Auto(parent) is compiled down to a constructor function by babel. The error happens in node_modules/uniforms/AutoForm, which is already compiled. So It seems, I can't fix it in my codebase

Edit2: The error already happens in

import ValidatedForm from 'uniforms/ValidatedForm';

import BaseForm from './BaseForm';

const Validated = parent => class extends ValidatedForm.Validated(parent) {
  static Validated = Validated;
};

where

class extends ValidatedForm.Validated(parent) {
  static Validated = Validated;
};

gives a "broken" class, that cannot be instantiate

radekmie commented 6 years ago

I’ll reopen it then.

macrozone commented 6 years ago

@radekmie any idea what we can do? I can try to setup a reproduction on the weekend, but it seems i am not into babel enough to be able to fix this issue myself.

radekmie commented 6 years ago

Have you tried to launch the demo app? It's working somehow. I'll try to do some research again, but I can't promise anything.

macrozone commented 6 years ago

@radekmie

not yet, but i will try

the demo app also uses preset-env, which should not be used according to this comment: https://forums.meteor.com/t/meteor-1-7-and-babel-issues/44479/4?u=macrozone

radekmie commented 6 years ago

If you have a better idea on how to make it work, I'd really appreciate that and surely update the demo project. As for now, it's working pretty well.

macrozone commented 6 years ago

I tried the demo app. It works, unless i remove ["@babel/preset-env", {"useBuiltIns": "usage"}],

as you should not use "@babel/preset-env" in a meteor app, i think you basically will break meteor 1.7's modern/legacy optimisation.

maybe @benjamn has an idea, what to do in this situation.

Edit: If i add ["@babel/preset-env", {"useBuiltIns": "usage"}], to my app, I will get an error " Missing class properties transform." while bundling. If i then further add "transform-class-properties", I will get other Class constructor XXX cannot be invoked without 'new' on other npm imports.

So I strongly believe it has to work without ["@babel/preset-env", {"useBuiltIns": "usage"}], or we will break a lot of apps

macrozone commented 6 years ago

ok, the problem seems to be much harder:

for inheritence to work, the whole class chain has to be either es5 polyfilled or all es6 classes. A transpiled es5 pseudo-class can't extend a es6 class otherwise you get the error TypeError: Class constructor _class cannot be invoked without 'new'.

But this might happen with uniforms e.g. in connectField:

return class extends baseField {

where baseField can be passed from your app's code (from your "app-space")

In case of meteor 1.7. this baseField will be a native es6 class for modern browsers, but uniforms will use it as a base class for a es5 transpiled class (in "module-space").

I think the only proper solution is to enable meteor to transpile the raw code to either es6 or es5 depending on the target. So uniform should additionaly ship es6 code.

Edit: some more thoughts: https://github.com/meteor/meteor/issues/9934#issuecomment-406888390

macrozone commented 6 years ago

possible solution:

radekmie commented 6 years ago

All uniforms packages are also shipping the ES6 version. You can check it in your node_modules directory or on unpkg.com/uniforms/, directory src/.

macrozone commented 6 years ago

@radekmie, yes simlinking src into the app seems to work!

radekmie commented 6 years ago

I think we can close it as we already have a workaround.

mariusrak commented 6 years ago

Would it be possible to solve the issue without workaround, please? @radekmie you claim that uniforms are shipped in ES6 and I believe that up-to-date meteor and babel produce also ES6 so what is causing the error?

I would like to use uniforms as standard package, with all the updates and convinience. So I don't like the workaround very much :)

Thanks.

radekmie commented 6 years ago

Hi @mariusrak. Yes, both transpiled and non-transpiled versions are published - you can see it on unpkg.com. I don't know what is causing the error - I'd surely fix it if I knew.

macrozone commented 6 years ago

@mariusrak @radekmie

I explained above what causes the issue (see https://github.com/vazco/uniforms/issues/433#issuecomment-406887427)

The only workaround possible at the moment is the symlinking described above https://github.com/vazco/uniforms/issues/433#issuecomment-406889261

mariusrak commented 6 years ago

@macrozone, I've read that, but I somehow don't understand. If both uniforms and output of meteor project are in ES6, then where is the problem?

macrozone commented 6 years ago

@mariusrak if you do a import XXX from "uniforms", XXX is ES5 code, because that is what is shipped to npm. It addionally has ES6 code in "uniforms/src", but that is not automatically used.

mariusrak commented 6 years ago

So is it possible to ship ES6 code to npm? Or why not?

macrozone commented 6 years ago

@mariusrak yes, you can do that. But then it only works in environments that support es6 or transpile it down.

Even meteor does not automatically transpile code from npm/node_modules unless you do the symlink-trick.

I think @benjamn is working on that problem, but its not easy. Here is a good read: https://babeljs.io/blog/2018/06/26/on-consuming-and-publishing-es2015+-packages

macrozone commented 5 years ago

ok, the same problem happens if you use other bundlers. E.g. it happend to me on a jest / storyshots test now.

@radekmie what maybe would help is to transpile also to es201x and use a module entry point. Modern bundlers would then take the untranspiled code. This also improves tree-shaking usually

radekmie commented 5 years ago

Tree shaking is not really an interesting case for us, as one can import uniforms/X anyway (and we encourage to do so in the docs). Yes, we can do it, but we are not planning to do so. We've migrated to TypeScript recently (it's still unreleased) - maybe that will help.

macrozone commented 5 years ago

@radekmie i could somewhat work around the issue.

its really easy to support es-module, e.g. see the first three changes here: https://github.com/react-page/react-page/pull/699/files

many bundlers (e.g. webpack) will then pickup module entrypoint and use the uncompiled es20xx code there. You can even use dynamic imports for code-splitting then.

refactorized commented 5 years ago

Would it be possible to just skip ~objects~ classes altogether? what is extension but saying an object gets it's parent functions and properties; Es6 return { newProp, newFn, ...oldObj } can do all that without the hubabaloo of classes and interoperability between es5 and esNext, stuff just works.

macrozone commented 5 years ago

damn, run again into that problem. Is there another way to extend the base forms?

@refactorized yeah, i try to avoid classes like the plague...

macrozone commented 5 years ago

@radekmie i could somewhat work around the issue.

its really easy to support es-module, e.g. see the first three changes here: https://github.com/react-page/react-page/pull/699/files

many bundlers (e.g. webpack) will then pickup module entrypoint and use the uncompiled es20xx code there. You can even use dynamic imports for code-splitting then.

@radekmie maybe we can try that? Problem is you can't test that with meteor as it does not support the module entry point. but at least most other bundlers should work

possible workaround:

when using next.js, you might need to configure:

[
        "next/babel",
        {
          "preset-env": {
            include: ["@babel/plugin-transform-classes"],
            modules: "commonjs",
          },
        },
]

this will compile classes always down to oldschool es5 even if they are supported. This will unfortunatly increase the bundle size a bit and decrease probably the performance.

radekmie commented 5 years ago

It took some time, but we are back, finally with some ideas. We are far too deep in classes to "go back" now (maybe in the future; never say never, huh?), so let's cope with it somehow. We've done some research on how to ship all needed code (both es5 and es6) and not to break everything. A PR will be opened in a second and I'm kindly asking both of you to check it.

macrozone commented 5 years ago

@radekmie cool! thx! looks similar to what i did in another project https://github.com/react-page/react-page/pull/699/files

Floriferous commented 5 years ago

Just ran into this again, since it doesn't seem like a reproduction was ever extracted, here is one: https://github.com/Floriferous/meteor-cypress-debug/tree/bug/class-constructor

EDIT: Workaround for me at the moment is to change this:

// Failing
import AutoForm from "uniforms-material/AutoForm";
import AutoField from "uniforms-material/AutoField";

// Working
import { AutoForm, AutoField } from "uniforms-material";
CaptainN commented 4 years ago

I just came across this too - ended up using the destructuring import style, the babel plugin "babel-plugin-transform-imports" and meteor 1.8.2's recompile flag to make it work:

{
  "babel": {
    "plugins": [
      [
        "transform-imports",
        {
          "@material-ui/core": {
            "transform": "@material-ui/core/${member}",
            "preventFullImport": true
          },
          "@material-ui/icons": {
            "transform": "@material-ui/icons/${member}",
            "preventFullImport": true
          },
          "@material-ui/styles": {
            "transform": "@material-ui/styles/${member}",
            "preventFullImport": true
          },
          "uniforms": {
            "transform": "uniforms/src/${member}",
            "preventFullImport": true
          },
          "uniforms-material": {
            "transform": "uniforms-material/src/${member}",
            "preventFullImport": true
          }
        }
      ],
      "lodash",
      "npdev-react-loadable-babel"
    ]
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.js",
      "server": "server/main.js"
    },
    "nodeModules": {
      "recompile": {
        "uniforms": true,
        "uniforms-material": true
      }
    }
  }
}
hmvp commented 4 years ago

For future reference there is more discussion and some solutions in #612 .

I could not get the tests in my modified copy of uniforms-unstyled to work in a Create react app project due to Jest ignoring the es6 modules and only limited ways to configure jest properly. The production code worked fine though

My solution was to remove all the form (Autoform, Quickfom etc) files from my copy and just import them from the official uniforms-unstyled. Their direct interaction with the field files is not existent and I did not need to modify them.. It where those tests that were failing..