New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ES6 compile target causes "Cannot call class as function" error #2
Comments
Is this a babel-specific issue? Does this cause errors for you when you compile or at runtime? |
yes it happen at runtime |
I'll need more details in order to reproduce the issue. It sounds like it may be specific to babel, which makes me hesitant to investigate further. I'm closing this for now, but if you could reproduce the issue within the test suite I'd gladly look into it further. |
It's easy to reproduce. Just following code:
with
Because ts will transform class direct as class if target is set to 'es6'. |
Thank you for adding more information. I now see the issue. I will have to think of the best way to resolve that. |
As far as I can tell, this unfortunate change from es5 to es6 is incompatible with the library. There's no way to call a constructor as a function without using Out of curiosity, is there any reason you must target es6? Targeting es5 doesn't cause these issues. |
The reason is that our project had never limit our target to only ES5... :) |
Well, the only suggestion I can give you is to not target ES6 for now. Calling constructor functions without I do appreciate the issue submission and I'm sorry there's not a better solution at the moment. |
I had a bit of a play with ES6 proxies. With some more work, perhaps this is a viable route to go for ES6?
|
Thanks for looking into this! I have a few concerns though: Primarily, adding Proxies may work fine for ES6 compilation, but it will have to be polyfilled for earlier targets, which seems a bit overkill. Also, I'm not quite sure what purpose the Proxy serves here. Could you elaborate? |
In ES6 you can't invoke the constructors as you've been doing (as is the subject of this issue). An alternative is to construct them in the normal manner and then compose them using a Proxy so that the proxy appears to have the union of all the properties of the classes being mixed in. The major difference will be the handling of "this", but there is probably some rebinding that could be done to set "this" to be the proxy (or the final class) if that is desirable. In my code snippet, Mixin() returns a new class that has the proxy as it's prototype. This allows a class to derive from Mixin( A, B, ... ) just like your original implementation, making this a drop-in replacement that is TypeScript friendly. There is no way to polyfill Proxy, so there would be two implementations, with the appropriate on selected at runtime. |
Thank you for the explanation. I think I understand what you're going for, but With that said, I might be able to be convinced to go down the Proxy route, but I'm still hesitant:
I'd much rather see an implementation that constructs the instances and manually merges them instead of using a Proxy and introducing more complexity and compatibility issues. But even then, while it may work for most use-cases, there are ways that usages of I don't want to shoot down your idea, just talking through my concerns. |
Yes, the target of the Proxy is an array of objects. The traps (only partially implemented) on the Proxy search this array of objects for objects to as required. Just a couple of comments on your three bullet points:
I think that a library that requires users to target ES5 will become irrelevant over time (eg, I can't use it as I'm targeting ES6, but would have otherwise), so hopefully this idea provides at least one direction for you. Best of luck - I'll leave this with you to mull over! |
Ah, I should have read the proxy handler more carefully. My apologies. ES5 becoming irrelevant over time is definitely a compelling reason to reconsider this issue... Proxies may be the way to go. On a different note, I've been pondering different ways one could "extract" a callable function given an ES6 class. Forgive my poor sketch (I'm low on sleep), but perhaps something like this could open new possibilities: function classToFunction(clazz) {
const source = clazz.toString();
const constructorSource = /* use regex to extract body of constructor */;
const constructorParams = /* use regex to extract constructor parameters */;
return new Function(...constructorParams, constructorSource);
} |
Something like that could work, but I'm sure that there would be some interesting cases to cover, like handling calls to super class constructors. Personally I feel like extracting code and then eval'ing should be a last resort, but if other options are exhausted then so be it. Another option is to extend TypeScript through a Transformer and fiddle with the generated JS code that way. But, my limited experience with Transformers was not a particularly pleasant one (trying to get various webpack loaders to use the Transformer was arduous), but maybe things have improved. Ideally Microsoft would include a way to load transformers in the standard TypeScript compiler. |
I definitely agree that extracting and eval()ing code from a string feels very hackey. However, it avoids the compatibility issues previously discussed, and I think I have an idea on how to handle the |
Just thinking aloud - what happens with functions that are stringified and then eval'd in a different context, but they depend on variables defined in the original context, like imported modules or variables captured by a closure? Will this approach only work for a narrow set of use cases? |
Oh shoot, you're right... That might be impossible to get around too. The super call issue could be worked around because you can access the superclass constructor directly with |
Sorry for such a long delay. Just wanted to give a quick update: I've exhausted several options to fix this issue, and for now the most practical, non-complicated solution is to simply use // ...
class Mixed {
constructor(...args) {
for (const constructor of ingredients) {
// If the constructor is a callable JS function, we would prefer to apply it directly to `this`,
if (!isClass(constructor)) constructor.apply(this, args);
// but if it's an ES6 class, we can't call it directly so we have to instantiate it and copy props
else Object.assign(this, new constructor(...args));
}
}
}
// attach prototypes, static props, etc... A bug in the TypeScript compiler is getting in the way of publishing a |
v3.0.0 has been released, which should resolve this issue. |
Our project use babel-typescript-plugin as compiler, in generated code, here is:
I see that ts-mixer call constructor explicitly.
See babel/babel#700 (comment)
The text was updated successfully, but these errors were encountered: