babel-plugin-transform-class-inherited-hook
Babel plugin that transforms subclass declarations to call superclass.onInherited afterwards (if present).
Example
Before:
class Apple extends Fruit {
tastiness() {
return 7;
}
}
After:
var Apple = function () {
var _Apple = class extends Fruit {
tastiness() {
return 7;
}
}
Object.defineProperty(_Apple, "name", { value: "Apple", configurable: true });
if ("onInherited" in Fruit) {
if (typeof Fruit.onInherited == 'function') {
var _Apple2 = Fruit.onInherited(_Apple);
if (_Apple2 !== undefined) {
if (typeof _Apple2 == 'function' && _Apple2.name !== "Apple") {
Object.defineProperty(_Apple2, "name", { value: "Apple", configurable: true });
}
_Apple = _Apple2;
}
} else {
throw new TypeError("Attempted to call onInherited, but it was not a function");
}
}
return _Apple;
}();
NOTE: Actual implementation uses a helper function so this logic isn't repeated every single class declaration.
What?
Every class declaration with a superClass gets transformed into an expression that:
- Creates the child class
- calls
SuperClass.onInherited(ChildClass)
if present - evaluates to the return value of
SuperClass.onInherited(ChildClass)
if any, otherwise the created child class
Why?
This lets you hook class inheritance:
function register(klass){ ... }
class RegisteredItem {
static onInherited(child) {
console.log(`A new registered item class was created: ${child.name}`);
register(child);
}
}
It also lets you transform classes at inheritance time:
class Polyfill {
static needsToBeShimmed() {
return true;
}
static nativeClass() {
return null;
}
static onInherited(child) {
let { needsToBeShimmed, nativeClass } = child;
if (!needsToBeShimmed()) {
return nativeClass();
}
}
}
class Promise extends Polyfill {
static needsToBeShimmed() {
return !window.Promise;
}
static nativeClass() {
return window.Promise;
}
constructor(func) {
...
}
}
No really, what are some practical uses?
Ok, how about automatically calling react-redux's connect
method?
import React from 'react';
import { connect } from 'react-redux';
class ConnectedContainer extends React.Component {
static onInherited(child) {
let { mapStateToProps, mapDispatchToProps, mergeProps } = child;
return connect(mapStateToProps, mapDispatchToProps, mergeProps)(child);
}
}
}
class NiceFlowerbed extends ConnectedContainer {
static mapStateToProps(state, ownProps) { ... }
}
Or maybe generating a reducer from a class with action handler methods?
import { handleActions } from 'redux-actions';
class Reducer {
static onInherited(child) {
return handleActions(child.prototype);
}
}
class Magic extends Reducer {
CAST_SPELL(state, action) { ... }
LEARN_SPELL(state, action) { ... }
}
You could even do the unthinkable...
let ActiveRecord = {
Base: class {
static onInherited(child) {
associateWithDatabaseTable(child, child.name);
definePropertyAccessorsUsingAttributes(child, child.name);
child.find = createDatabaseFinderMethod(child, child.name);
}
save() { ... }
}
};
class User extends ActiveRecord.Base {}
assert(User.find(1).name === "Bob");
But this makes class extension untrustworthy and changes the semantics of a well-defined system!
Yeah, it does. But it can help provide a little bit of structure and get rid of a little boilerplate.
Installation
$ npm install --save babel-plugin-transform-class-inherited-hook
Usage
Via .babelrc
(Recommended)
.babelrc
{
"plugins": ["transform-class-inherited-hook"]
}
Via CLI
$ babel --plugins transform-class-inherited-hook script.js
Via Node API
require('babel-core').transform('code', {
plugins: ['transform-class-inherited-hook']
});
Thanks