Extending Built-In Natives in Es6 with Babel

Extending built-in natives in ES6 with Babel

Extending native classes is not supported by Babel.
It was removed in version 5.2.17 (see this commit)

It was removed because it was not working properly, see the bug: https://phabricator.babeljs.io/T1424

It's unlikely it will be ever added because it's not a feature that can be simulated. We will have to wait for native support in browsers (some already support it now in experimental mode). That also means it will currently behave differently in different browsers.

How can I extend the Array class in Babel?

I'm not sure about Babel support but node.js started supporting (partially) Array subclassing since version 4.3.2 and implemented full subclassing support in version 6.8.1 (http://node.green/#Array-is-subclassable). So if you're using node 6 Array subclassing should work.

If Babel is failing you you can always do it the "classical" way:

// Code tested and works in 6.3.1:
function Stack = () {};
Stack.prototype = Object.create(Array.prototype);
Stack.prototype.last = function () {
return this[this.length - 1];
}

var s = new Stack();

Extending MediaSource with Babel -- how to properly call super()?

Generally extending builtin types does not work with compiled classes from Babel, so you'd need to configure Babel to not process classes, and limit your application to only browsers that support classes.

Assuming your target browsers all support ES6 class syntax, the easiest approach would be to use babel-preset-env configured for those target environments.

You can also try to use transform-builtin-extend in your Babel config for MediaSource, though that does tend to vary with exactly which things can be extended.

Extending Built-in Array With ES6 Classes - Method is not a function error

UPDATED ANSWER

To answer your modified question, TypeScript doesn't let you extend built-ins like Array, Error etc. The reason is written here.

In ES2015, constructors which return an object implicitly substitute
the value of this for any callers of super(...). It is necessary for
generated constructor code to capture any potential return value of
super(...) and replace it with this.

As a result, subclassing Error, Array, and others may no longer work
as expected. This is due to the fact that constructor functions for
Error, Array, and the like use ECMAScript 6's new.target to adjust the
prototype chain; however, there is no way to ensure a value for
new.target when invoking a constructor in ECMAScript 5. Other
downlevel compilers generally have the same limitation by default.

So if you must extend built-in Array under ES5 environment, then you can try Babel for compiling your code. However be aware that it has it's limitation as stated here.

Built-in classes such as Date, Array, DOM etc cannot be properly
subclassed due to limitations in ES5 (for the transform-classes
plugin). You can try to use babel-plugin-transform-builtin-extend
based on Object.setPrototypeOf and Reflect.construct, but it also has
some limitations.

OLD ANSWER

While the code itself is perfectly find and also executes fine in the browser, I think the error you are getting is because of the TypeScript compiler.

For the source code

interface Animal {
name: string;
weight: number
}

class AnimalCollection extends Array <Animal> {
constructor(name: string, ...items: Animal[]) {
super(...items);

Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
})
}

findAnimal(name:string): Animal | null {
return this.find(a => a.name === name) || null;
}
}

const animalsArray = [
{name: 'TD-23', weight: 60},
{name: 'TD-25', weight: 50},
{name: 'TXD-26', weight: 120},
{name: 'TYD-26', weight: 40}
];

const animals = new AnimalCollection('Deers', ...animalsArray)

console.log(animals.findAnimal('TD-23'));

If the compiler target option is set to ES5, then it produces code that breaks the implementation. The code it generates is

"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var AnimalCollection = /** @class */ (function (_super) {
__extends(AnimalCollection, _super);
function AnimalCollection(name) {
var items = [];
for (var _i = 1; _i < arguments.length; _i++) {
items[_i - 1] = arguments[_i];
}
var _this = _super.apply(this, items) || this;
Object.defineProperty(_this, 'name', {
enumerable: false,
writable: false,
value: name
});
return _this;
}
AnimalCollection.prototype.findAnimal = function (name) {
return this.find(function (a) { return a.name === name; }) || null;
};
return AnimalCollection;
}(Array));
var animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
var animals = new (AnimalCollection.bind.apply(AnimalCollection, __spreadArrays([void 0, 'Deers'], animalsArray)))();
console.log(animals.findAnimal('TD-23'));

However, if we set the target in tsconfig.json to something equal to or greater than ES2015, then the code it generates is

"use strict";
class AnimalCollection extends Array {
constructor(name, ...items) {
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
});
}
findAnimal(name) {
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
const animals = new AnimalCollection('Deers', ...animalsArray);
console.log(animals.findAnimal('TD-23'));

Which of course works. So I think there is an issue in TypeScript compiler for version ES5 or less, which breaks the implementation. I have tried using Babel for compilation and it works for ES5.

Babel can't properly extend Array?

Babel.js can't extend the built-in classes properly:

Built-in classes such as Date, Array, DOM etc cannot be properly subclassed due to limitations in ES5 (for the es2015-classes plugin). You can try to use babel-plugin-transform-builtin-extend based on Object.setPrototypeOf and Reflect.construct, but it also has some limitations.

-- https://babeljs.io/docs/usage/caveats/#classes

ES6 Class extends Array: workaround for ES5 Babel transpile

After some discussions here I was able to build a solution satisfied both of the requirements: keep original ES6 class as a state for new functionality and have this new functionality on the prototype as well as it is for the Array.prototype methods.

class CacheProto {
add(item) {
if(!item.doNotRemove)
this.push(item)
}
printLast() {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
}

function Cache() {
const instance = [];
instance.push.apply(instance, arguments);
Object.setPrototypeOf(instance, Cache.prototype);
return instance;
}
Cache.prototype = Object.create(Array.prototype);
Object.getOwnPropertyNames(CacheProto.prototype).forEach(methodName =>
Cache.prototype[methodName] = CacheProto.prototype[methodName]
);

The only difference between this CacheProto and the original class from the Question is that the CacheProto class does not extend the Array.

The end plunker could be obtained here. It contains this solution and all intermediate variants.

Mocha+Babel replaces class that extends array by array

I'm assuming you're using Babel to transpile this to ES5.

Array doesn't behave quite the same as other constructors. There isn't really an equivalent syntax for doing this in ES5, because to some extent it was not even really possible in ES5. As such, what Babel generates in attempt to create equivalent syntax will not work the way you'd expect it to.

If you take a look at the generated code, you'll find this line:

return _possibleConstructorReturn(this, (A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments));

At this point, A.__proto__ will be Array, and the constructor was given no arguments, so this is calling Array.apply(this, []) and passing the result to the call argument of _possibleConstructorReturn.

In _possibleConstructorReturn, you'll find this line:

return call && (typeof call === "object" || typeof call === "function") ? call : self; }

In most cases, calling apply on a constructor (invoking it without new) will return undefined. Thus, this function will return the self parameter, which is the this from the A contructor invocation.

Array does not behave like a normal constructor, however. If you invoke it without new, it will still return an Array. So what you get coming out of your A constructor is just a plain old array.

There may be ways around this, but Babel does not support any. To be honest, if you need your code to work in ES5, you probably shouldn't try to extend arrays like this. :\

What often works as an alternative is to write a simple class that stores an array as a property, with wrapper methods for the array operations you need. Say a get method to use in place of square bracket property access, a push that simply invokes push on the wrapped array.

Extending Error in Javascript with ES6 syntax & Babel

Based on Karel Bílek's answer, I'd make a small change to the constructor:

class ExtendableError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
}
}
}

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

This will print MyError in the stack, and not the generic Error.

It will also add the error message to the stack trace - which was missing from Karel's example.

It will also use captureStackTrace if it's available.

With Babel 6, you need transform-builtin-extend (npm) for this to work.

transform-builtin-extend seems does not working

You have to set the approximate option to false. From the plugin GitHub repository:

On older browsers that do not support reassigning the prototype of an existing object, you will need to enable the approximate mode, which will fall back to the Babel 5 behavior of using simple ES5 inheritance to approximate extending a class, though your results may vary depending on your goals.



Related Topics



Leave a reply



Submit