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
Native extends breaks HTMLELement, Array, and others #4480
Comments
Without class Class extends Date {};
+new Class(); // => ?
class A {
constructor(){
console.log(this.constructor.name);
}
}
class B extends A {
constructor(){
super();
console.log(this.constructor.name);
}
}
class C extends B {
constructor(){
super();
console.log(this.constructor.name);
}
}
new C; // => ? |
BTW do you know about https://github.com/loganfsmyth/babel-plugin-transform-builtin-extend? |
Right, so ... I've written probably a very poor version of the non Let me try again with an already (manually) transpiled code function reConstruct(Parent, args, Child) {
var boundArgs = [this];
boundArgs.push.apply(boundArgs, args);
return new(Parent.bind.apply(Parent, boundArgs));
}
function test(name) {
return Function('reConstruct', 'return ' + (function Child() {
// the super call bit
var
instance = reConstruct.call(
this,
Object.getPrototypeOf(Child),
arguments,
Child
),
type = instance ? typeof instance : '',
self = (type === 'object' || type === 'function') ?
Object.setPrototypeOf(instance, Child.prototype) :
this
;
// the rest of the constructor body
console.log(self.constructor.name);
// at the end or instead of empty returns
return self;
}).toString().replace(/Child/g, name))(reConstruct);
}
var A = function A() {
console.log(this.constructor.name);
};
var B = ec5end(test('B'), A);
var C = ec5end(test('C'), B);
function ec5end(C, P) {
C.prototype = Object.setPrototypeOf(
C.prototype,
P.prototype
);
return Object.setPrototypeOf(C, P);
} so basically this is the change: var
reConstruct = typeof Reflect === 'object' ?
Reflect.construct :
function (Parent, args, Child) {
var boundArgs = [this];
boundArgs.push.apply(boundArgs, args);
return new(Parent.bind.apply(Parent, boundArgs));
}
; Outstanding problems: the log is A, B, C but it's a relatively minor issue compared with how much is currently "broken" the resulting transpiled code. |
Yes, IIRC it doesn't scale well with Is this how it works? |
@WebReflection We could certainly expand the functionality of that module, either with a whitelist, or the ability to just say "all globals" or something, thoughts? There's the possibility of a solution like #3582, but it's unlikely that we could land a generic solution like you have proposed because calling |
I will find out a way to generate at runtime the right code accordingly with the kind of extend is needed. The entry point to solve this is a native parent, subclasses after that can already be somehow simplified. The I'd love to have data though. |
Update function requiresUpgrade(Class) {
return !Object.getOwnPropertyDescriptor(
Class,
'prototype'
).writable;
} This seems to be consistent with all natives but also with native ES6+ classes. This makes it possible to decide at definition time if the transpiled function needs more operations or it can simply use the current logic. Thoughts? If this looks OK I can move forward with the rest of the implementation details. |
I am not sure if this is directly connected to this issue, but Babel class transpilation breaks using native I have found an article The JS super (and transpilers) problem with possible solution (last code example). I created two codepen examples. Babel transpiled class (http://codepen.io/smalluban/pen/wzgoON?editors=0012):
Firefox/IE uses customElements polyfill, so it works well - there is no problem with Custom transpiled class (http://codepen.io/smalluban/pen/JREbaW?editors=0012):
In this example if I am not sure how this will work with "normal" class definition where special behavior is not required. |
@smalluban yes, it's related, and Custom Elements extends are indeed the reason I came here. |
Even if some inheritance is done wrong, there always is this: function Test() {}
Test.prototype.valueOf = function() { return 3; };
+new Test(); // 3
// and even
Date.prototype.valueOf = function() { return 5; };
+new Date(); // 5 |
It looks like we all agree on this point: WICG/webcomponents#587 (comment) Not even browser vendors developers can workaround the current broken state when it comes to I won't have time soon to solve this issue, I'd like to understand if it has any priority though so at least I can better reply to people asking me why my polyfill is broken ( when the issue is them using Babel :-( ) Thanks for any sort of ETA / outcome |
@zloirock So is there a fix coming for this? It basically makes web components / custom elements unusable. 😢 |
Once again, the idea behind (but I'm not familiar with Babel and I don't have time now to push a PR) is to change the transformation so that writing this class: class List extends Array {
constructor(a, b, c) {
super(a, b);
this.push(c);
}
} should produce the following: "use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var List = function (_Array) {
_inherits(List, _Array);
var _retrieveThis = Object.getOwnPropertyDescriptor(_Array, 'prototype').writable ?
// user defined class or function
function (Parent, a, b) {
return _possibleConstructorReturn(this, Parent.call(this, a, b));
} :
// native class
function (Parent, a, b) {
// eventually it needs a Reflect and Object.setPrototypeOf polyfill upfront
return Object.setPrototypeOf(Reflect.construct(Parent, [a, b], List), List.prototype);
};
function List(a, b, c) {
_classCallCheck(this, List);
var _this = _retrieveThis.call(this, (List.__proto__ || Object.getPrototypeOf(List)), a, b);
_this.push(c);
return _this;
}
return List;
}(Array); At this point the following code would work as expected: var l = new List(1, 2, 3);
l.length; // 3
l; // [1, 2, 3]
l instanceof Array; // true
l instanceof List; // true The check Above transformation would work with every modern browser that supports native classes and Reflect, which would be already a very wide variety of devices and browsers. In alternative, we might need a solution that fallbacks in order browsers when it comes to simulate Although having the direct Does this help anyone speeding up the release of a patch for this issue? |
Hi, As a workaround on my side, I use For info, I am using jspm 0.17.0-beta.31 with babel 6.16.0. Here's an example with a basic class: in ES6
Transpilled with babel runtime
Hope it helps a bit. |
@stephanbureau remember in V1 there are changes so that you need to specify upfront attributes to listen to. class MyDom extends HTMLElement {
static get observedAttributes() {
return ['country'];
}
attributeChangedCallback(name, oldValue, newValue) {
// react to changes for name
alert(name + ':' + newValue);
}
}
var md = new MyDom();
md.setAttribute('test', 'nope');
md.setAttribute('country', 'UK'); // country: UK |
Amazing, that was the missing piece for me. Thanks. Sorry for the noise on the issue. |
Due to documented problems of transpiled ES2015 classes and extending native constructors (babel/babel/issues/4480), I had to use the V0 api, it all works fine in development but build is broken
@aaronshaf how does that help with the constructor problem ? |
By the way, I'm strongly in favour of adding something like that - #1172, but not so complex version (a special case for built-ins without multilevel subclassing). It was added in early versions on |
@zloirock have you seen my proposed changes ? You can compare them directly with the current output generated by class List extends Array {
constructor(a, b, c) {
super(a, b);
this.push(c);
}
} Current "use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var List = function (_Array) {
_inherits(List, _Array);
function List(a, b, c) {
_classCallCheck(this, List);
var _this = _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).call(this, a, b));
_this.push(c);
return _this;
}
return List;
}(Array); Improved "use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var List = function (_Array) {
_inherits(List, _Array);
var _retrieveThis = Object.getOwnPropertyDescriptor(_Array, 'prototype').writable ?
// user defined class or function
function (Parent, a, b) {
return _possibleConstructorReturn(this, Parent.call(this, a, b));
} :
// native class
function (Parent, a, b) {
// eventually it needs a Reflect and Object.setPrototypeOf polyfill upfront
return Object.setPrototypeOf(Reflect.construct(Parent, [a, b], List), List.prototype);
};
function List(a, b, c) {
_classCallCheck(this, List);
var _this = _retrieveThis.call(this, (List.__proto__ || Object.getPrototypeOf(List)), a, b);
_this.push(c);
return _this;
}
return List;
}(Array); The only overhead for non native constructors is during class definition time (so once per Application lifecycle) through: Object.getOwnPropertyDescriptor(_Array, 'prototype').writable Hopefully, that's practically irrelevant for real-world projects. console.time('gOPD');
for(let i = 0; i < 1000; i++)
Object.getOwnPropertyDescriptor(Array, 'prototype').writable;
console.timeEnd('gOPD');
// gOPD: 0.582ms for thousand classes |
@WebReflection looks good, but I'm not sure about |
@zloirock it's in specs, it should be consistent with every ES5 compatible browser and, if polyfilled properly in ES5-shim, down to older browsers/engine too. Since babel transform every class into ES3 compatible functions, that check will always be |
@WebReflection I don't see any fixes for this difference in |
@zloirock that's eventually a possible bug/fix for ES5 shim. Meanwhile, all ES5 compatible browsers would benefit from this update ... right? |
FYI the plugin has been updated after a fix related to N level inheritance. There are also tests now to validate it works on NodeJS. Custom Elements also seem to work without any issue with or without my polyfill. I believe all my tests are using the Best Regards |
@WebReflection not sure what the others think yet, but we could move this into the main transform and make it an option there? Seems like it could be a relatively simple port |
@hzoo dunno what to answer. I have no idea how Babel proceeds but I'm sure the world would appreciate a fix in core for the most annoying untranspiled thing ever 😄 I've tested here and there my latest changes and while I'm sure there will be some edge weird case to eventually document, it worked well for all my Custom Elements and other native extends cases. I've also tested browsers without Reflect support and it looks like everything is just fine. To me it'd be an OK 👍 to proceed, but it also needs some documentation around, 'cause developers need to understand they have to specify upfront which native constructor they'd like to extend. Please let me know what I can do to move forward, thanks. |
Maybe we should close this as won't fix since there's no activity whatsoever around this bug? I sometimes go through my GitHub issues and this one looks like one of those that will be there forever. Please let me know if there's anything I should do or if I can just drop it, thanks. |
We should at least add a link to the |
I am OK with it, but not sure which two plugins you are talking about. Everything but keeping this pointlessly open works for me. |
Thanks everyone for making this happen - and of course @WebReflection in particular for figuring this out in the first place and staying on the ball. For users who aren't familiar with Babel's internals, AFAICT the fix in #7020 will be included by default with the upcoming Babel v7.0? (If you're using something like @babel/preset-es2015 or @babel/preset-env, that is - at least those presets seem to depend on @babel/plugin-transform-classes.) |
Current Babel transform, when it comes to call the parent
It's a way too poor implementation.
If we take the current basic ES6 compat syntax:
We'll realize babel does a bad job.
The reason is simple: Babel replace the returns and exit, without caring about userland expectations.
This is how above basic extend should desugar:
It's a very ad-hoc case for the initial example that currently fails, but it's good enough to understand that inheriting the prototype is the least of the problem.
Indeed, we have 3 ways to do that within a transpiled code:
Solved the inheritance bit, considering Babel also set the prototype of each constructor,
we need to address cases where a
super
call might "upgrade" the current context, like it is forHTMLELement
or exotic native objects.Above case should desugar to something like the follwoing:
which is also ad-hoc example code for the previous example.
Considering a transpiler will get the arguments part easily right, this is how previous case could be generically transpiled for arguments used in both constructors:
The last problem is that modern syntax would use Reflect to create any sort of object, instead of old, ES3 friendly,
.call
or.apply
way.Following a past, present, and even future proof approach:
Above solution would work with userland, exotic, or DOM constructors, and in both native and transpiled engines.
The text was updated successfully, but these errors were encountered: