hermes-class

0.3.0 • Public • Published

hermes-class

Build Status Coverage Status CSP strict

A classtrophobic inspired approach to define Hermes compatible classes.

import Class from 'hermes-class';

const MySet = Class({
  static: {name: 'MySet'},
  extends: Set,
  super: [],
  get length() {
    return this.size;
  }
});

Why

Hermes doesn't support class yet, but pretty much everything else is in. As transpiling classes has been extremely awkward, in both Babel or TypeScript, and since about ever, this module provides a transpiler friendly way to define, and extend, classes,

The idea behind is that Hermes is already fairly compatible with ES2015, plus latest JS features, so that a lot of unnecessary overhead can be removed from previous attempt to make classes transpilation-proof, as it was for classtrophobic project.

Features

  • create generic classes or extend native classes with an ES6 friendly API based on object literal.
  • avoid undesired transpilation bloat and not fully expected behavior.
  • obtain great performance in Hermes for projects heavily based on classes.
  • define accessors and static properties, simplifying future refactoring.
  • allows using this.super(...) in constructor, as no-op, and proxied this.super.method(...) within methods only (not static).
  • optionally re-map received arguments in the constructor to the extend constructor.
const A = Class({
  constructor(a, c) {
    this.a = a;
    this.c = c;
  }
});
const B = Class({
  extends: A,
  super: [0, 2],
  constructor(a, b, c) {
    // implicit super arguments as:
    // A.call(this, arguments[0], arguments[2])
    this.b = b;
  }
});

Goals

  • provide an ad-hoc minimal overhead to define classes in Hermes, keeping well known semantics and keywords around for easy future refactoring.
  • try to perform as good as possible, keeping classes expectations around, without providing full ES2015 behavior, as that's hard, or messed up, even after transpilation (i.e. Symbol.species which is not supported in Hermes has no reason to be even polyfilled).
  • last, but not least, become obsolete as soon as Hermes classes become a reality.

Caveats

  • it is not possible to return a different context/instance from any constructor.
  • it is not possible to use this.super(...), within the constructor, in any meaningful way. Signatures are sealed with extends, and super invoked by default with the provided arguments. However, since signature have a precise length, it is possible to add extra, irrelevant, arguments, example:
const MySet = Class({
  extends: Set,
  constructor(alreadyPassed, value) {
    // super already invoked in here!
    // so the following is possible
    // but it effectively does nothing
    this.super(alreadyPassed);
    this.value = value;
  }
});

const ms = new MySet([1, 2, 3], "anything");
ms.size;  // 3, it contains 1, 2, and 3
ms.value; // "anything"

To obtain a method super call, either use an explicit prototype call (suggested), or the proxied dance:

const MySet = Class({
  extends: Set,
  add(value) {
    // suggested method (when performance matters)
    // return Set.prototype.add.call(this, value * 2);

    // provided proxied alternative
    return this.super.add(value * 2);
  }
});

const ms = new MySet;
ms.add(2);
ms.has(4);  // true

Benchmark

Following an hermes -jit test/benchmark.js run, using 0xFFF iterations over a native Set extend:

Fake Class Babel Class Hermes Class Hermes Class + super
creation 493ms 797ms 343ms 363ms
add(1) 54ms 71ms 42ms 893ms
has(1) 55ms 92ms 53ms 73ms
size 63ms 43ms 30ms 34ms
@@iterate 920ms 443ms 396ms 368ms

Fake Class

This is an old, repeated, bloated way, to simulate an extend without actually being an extend. This way is the fastest out there in NodeJS, but it shows not ideal performance in Hermes:

const internal = Symbol('internal');

function FakeClass(...args) {
  this[internal] = new Set(...args);
}

Object.setPrototypeOf(FakeClass.prototype, Set.prototype);
Object.defineProperties(FakeClass.prototype, {
  add: {value() {
    for (let i = 0; i < arguments.length; i++)
      this[internal].add(arguments[i]);
    return this;
  }},
  has: {value(value) {
    return this[internal].has(value);
  }},
  size: {get() {
    return this[internal].size;
  }},
  [Symbol.iterator]: {get() {
    return this[internal][Symbol.iterator].bind(this[internal]);
  }}
});

Babel Class

This class is the transpiled version of the following one:

class BabelSet extends Set {
  add() {
    for (let i = 0; i < arguments.length; i++)
    	super.add(arguments[i]);
    return this;
  }
}

Hermes Class

This is the suggested approach to explictly invoke super methods:

const {add: Set_add} = Set.prototype;

const HermesSet = Class({
  extends: Set,
  add() {
    for (let i = 0; i < arguments.length; i++)
      Set_add.call(this, arguments[i]);
    return this;
  }
});

Hermes Class + super

This class uses the super utility, but it is obvious it has a performance cost due runtime prototype swaps and &Proxy* creation and handling:

const HermesSetSuper = Class({
  extends: Set,
  add() {
    for (let i = 0; i < arguments.length; i++)
      this.super.add(arguments[i]);
    return this;
  }
});

Readme

Keywords

Package Sidebar

Install

npm i hermes-class

Weekly Downloads

35

Version

0.3.0

License

ISC

Unpacked Size

29.7 kB

Total Files

9

Last publish

Collaborators

  • webreflection