GithubHelp home page GithubHelp logo

mixwith.js's Introduction

mixwith.js

A simple and powerful mixin library for ES6.

mixwith differs from other mixin approaches because it does not copy properties from one object to another. Instead, mixwith works with "subclass factories" which create a new class that extends a superclass with the mixin - this is called a mixin application.

Example

my-mixin.js:

let MyMixin = (superclass) => class extends superclass {
  // mixin methods here
};

my-class.js

class MyClass extends MyMixin(MySuperClass) {
  // class methods here, go ahead, use super!
}

mixwith.js Helpers and Decorators

The subclass factory pattern does not require any support from a library. It's just a natural use of JavaScript class expressions. mixwith.js provides a few helpers that make mixins a little more powerful and easier to use.

mixwith.js makes some use cases very easy:

  • Determine if an object or class has had a particular mixin applied to it.
  • Cache mixin applications so that a mixin repeatedly applied to the same superclass reuses its resulting subclass.
  • De-duplicate mixin application so that including a mixin multiple times in a class hierarchy only applies it once to the prototype type chain.
  • Add instanceof support to a mixin function.

mix().with()

mixwith.js also provides a little bit of sugar with the mix() function that makes applying mixins read a little more naturally:

class MyClass extends mix(MySuperClass).with(MyMixin, OtherMixin) {
  // class methods here, go ahead, use super!
}

Advantages of subclass factories over typical JavaScript mixins

Subclass factory style mixins preserve the object-oriented inheritance properties that classes provide, like method overriding and super calls, while letting you compose classes out of mixins without being constrained to a single inheritance hierarchy, and without monkey-patching or copying.

Method overriding that just works

Methods in subclasses can naturally override methods in the mixin or superclass, and mixins override methods in the superclass. This means that precedence is preserved - the order is: subclass -> mixin__1 -> ... -> mixin__N -> superclass.

super works

Subclasses and mixins can use super normally, as defined in standard Javascript, and without needing the mixin library to do special chaining of functions.

Mixins can have constructors

Since super() works, mixins can define constructors. Combined with ES6 rest arguments and the spread operator, mixins can have generic constructors that work with any super constructor by passing along all arguments.

Prototypes and instances are not mutated

Typical JavaScript mixins usually used to either mutate each instance as created, which can be bad for performance and maintainability, or modify a prototype, which means every object inheriting from that prototype gets the mixin. Subclass factories don't mutate objects, they define new classes to subclass, leaving the original superclass intact.

Usage

Defining Mixins

The Mixin decorator function wraps a plain subclass factory to add deduplication, caching and instanceof support:

let MyMixin = Mixin((superclass) => class extends superclass {

  constructor(args...) {
    // mixins should either 1) not define a constructor, 2) require a specific
    // constructor signature, or 3) pass along all arguments.
    super(...args);
  }

  foo() {
    console.log('foo from MyMixin');
    // this will call superclass.foo()
    super.foo();
  }

});

Mixins defined with the mixwith.js decorators do not require any helpers to use, they still work like plain subclass factories.

Using Mixins

Classes use mixins in their extends clause. Classes that use mixins can define and override constructors and methods as usual.

class MyClass extends mix(MySuperClass).with(MyMixin) {

  constructor(a, b) {
    super(a, b); // calls MyMixin(a, b)
  }

  foo() {
    console.log('foo from MyClass');
    super.foo(); // calls MyMixin.foo()
  }

}

API Documentation

apply(superclass, mixin) ⇒ function

Applies mixin to superclass.

apply stores a reference from the mixin application to the unwrapped mixin to make isApplicationOf and hasMixin work.

This function is usefull for mixin wrappers that want to automatically enable hasMixin support.

Kind: global function
Returns: function - A subclass of superclass produced by mixin

Param Type Description
superclass function A class or constructor function
mixin MixinFunction The mixin to apply

Example

const Applier = (mixin) => wrap(mixin, (superclass) => apply(superclass, mixin));

// M now works with `hasMixin` and `isApplicationOf`
const M = Applier((superclass) => class extends superclass {});

class C extends M(Object) {}
let i = new C();
hasMixin(i, M); // true

isApplicationOf(proto, mixin) ⇒ boolean

Returns true iff proto is a prototype created by the application of mixin to a superclass.

isApplicationOf works by checking that proto has a reference to mixin as created by apply.

Kind: global function
Returns: boolean - whether proto is a prototype created by the application of mixin to a superclass

Param Type Description
proto Object A prototype object created by apply.
mixin MixinFunction A mixin function used with apply.

hasMixin(o, mixin) ⇒ boolean

Returns true iff o has an application of mixin on its prototype chain.

Kind: global function
Returns: boolean - whether o has an application of mixin on its prototype chain

Param Type Description
o Object An object
mixin MixinFunction A mixin applied with apply

wrap(mixin, wrapper) ⇒ MixinFunction

Sets up the function mixin to be wrapped by the function wrapper, while allowing properties on mixin to be available via wrapper, and allowing wrapper to be unwrapped to get to the original function.

wrap does two things:

  1. Sets the prototype of mixin to wrapper so that properties set on mixin inherited by wrapper.
  2. Sets a special property on mixin that points back to mixin so that it can be retreived from wrapper

Kind: global function
Returns: MixinFunction - wrapper

Param Type Description
mixin MixinFunction A mixin function
wrapper MixinFunction A function that wraps mixin

unwrap(wrapper) ⇒ MixinFunction

Unwraps the function wrapper to return the original function wrapped by one or more calls to wrap. Returns wrapper if it's not a wrapped function.

Kind: global function
Returns: MixinFunction - The originally wrapped mixin

Param Type Description
wrapper MixinFunction A wrapped mixin produced by wrap

Cached(mixin) ⇒ MixinFunction

Decorates mixin so that it caches its applications. When applied multiple times to the same superclass, mixin will only create one subclass, memoize it and return it for each application.

Note: If mixin somehow stores properties its classes constructor (static properties), or on its classes prototype, it will be shared across all applications of mixin to a super class. It's reccomended that mixin only access instance state.

Kind: global function
Returns: MixinFunction - a new mixin function

Param Type Description
mixin MixinFunction The mixin to wrap with caching behavior

DeDupe(mixin) ⇒ MixinFunction

Decorates mixin so that it only applies if it's not already on the prototype chain.

Kind: global function
Returns: MixinFunction - a new mixin function

Param Type Description
mixin MixinFunction The mixin to wrap with deduplication behavior

HasInstance(mixin) ⇒ MixinFunction

Adds [Symbol.hasInstance] (ES2015 custom instanceof support) to mixin.

Kind: global function
Returns: MixinFunction - the given mixin function

Param Type Description
mixin MixinFunction The mixin to add [Symbol.hasInstance] to

BareMixin(mixin) ⇒ MixinFunction

A basic mixin decorator that applies the mixin with apply so that it can be used with isApplicationOf, hasMixin and the other mixin decorator functions.

Kind: global function
Returns: MixinFunction - a new mixin function

Param Type Description
mixin MixinFunction The mixin to wrap

Mixin(mixin) ⇒ MixinFunction

Decorates a mixin function to add deduplication, application caching and instanceof support.

Kind: global function
Returns: MixinFunction - a new mixin function

Param Type Description
mixin MixinFunction The mixin to wrap

mix([superclass]) ⇒ MixinBuilder

A fluent interface to apply a list of mixins to a superclass.

class X extends mix(Object).with(A, B, C) {}

The mixins are applied in order to the superclass, so the prototype chain will be: X->C'->B'->A'->Object.

This is purely a convenience function. The above example is equivalent to:

class X extends C(B(A(Object))) {}

Kind: global function

Param Type Default
[superclass] function Object

MixinFunction ⇒ function

A function that returns a subclass of its argument.

Kind: global typedef
Returns: function - A subclass of superclass

Param Type
superclass function

Example

const M = (superclass) => class extends superclass {
  getMessage() {
    return "Hello";
  }
}

mixwith.js's People

Contributors

adjohnson916 avatar bedeoverend avatar justinfagnani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mixwith.js's Issues

How do i import `mix` (ES6) ?

Hi, i'm trying already for a while to implement an utility to extend an LitElement with multiple classes that adds functionalities to it, but i want to avoid to create a mixin for every of these classes and execute them when extending. I want a simpler solution like yours seems to be.

The problem it's that i'm not able to import mix, neither import { mix } from 'mixwith'; or import mix from 'mixwith'; are working. I'm getting an error like this:
Uncaught (in promise) SyntaxError: The requested module '../../node_modules/mixwith/mixwith.js' does not provide an export named 'mix'

Can you please help me out ? Thanks

instance instanceof mixin?

Is there a way to do something like:

let MyMixin = Mixin((superclass) => class extends superclass {
    constructor() {
        super(...arguments);
    }
});
class MyClass extends mix(Array).with(MyMixin) {
    constructor() {
        super();
    }
}
var a = new MyClass();
console.log(a instanceof Array);
console.log(a instanceof MyClass);
console.log(a instanceof MyMixin);

Where the result would be:

true
true
true

Currently it returns:

true
true
Uncaught TypeError: Function has non-object prototype 'undefined' in instanceof check

error TS2339: Property 'emit' does not exist on type 'MyClass2'.

Hello!

The following code does not work. How do I fix this error?

import * as EventEmitter from 'events';

let MyMixin = Mixin((superclass) => class extends superclass {

  foo() {
    console.log('foo from MyMixin');
    super.foo();
  }

});

class MyClass extends mix(EventEmitter).with(MyMixin) {

  foo() {
    console.log('foo from MyClass');
    super.foo(); 
  }

}


class MyClass2 extends MyClass {
    
    method1() {
        this.emit('event1'); // error TS2339: Property 'emit' does not exist on type 'MyClass2'.
    }
    
}

Current Version Not In NPM?

I did a fresh install of mixwith via NPM and the newer (as of last month) omission of superclass within mix() code is not present. Do you have a timeframe for getting it into NPM?

#feature-request typescript version.

is this kind of mixin possible to be implemented in typescript?
I mean, the way it create a mixed subclass instead of attaching instanceMethod to the object prototype.

Alternative implementation (multiple inheritance with branching prototype chain)

Currently, mixwith creates a prototype chain where the mixin prototypes are between the subclass and super class.

Another possibility could be to use a Proxy and intercept the property get mechanism. The subclass would have a new prototypes property which is an array of prototypes (multi-inheritance). The get interceptor would loop through the prototypes and lookup the property being requested on each prototype. As for precedence, the proxy would loop from the end of the array and stop at the first prototype chain where the property is found.

Possible syntax:

class ThreeDeeObject extends multiple(Transform, EventEmitter, TreeNode) {}

where multiple returns a constructor who's prototype is the Proxy created by multiple.

If we then have

class Sphere extends ThreeDeeObject {}

then the prototype lookup works the same, but when it gets to the prototype of the multiple used in ThreeDeeObject's definition, then the lookup will use our get interceptor and basically lookup will branch into one of the prototypes in the prototypes array.

Just thought I'd jot the idea down here. I'm sure it has pros and cons compared to the mixin approach here.

setup misunderstanding?

Trying to get your library to work and could use some help.

I have an existing object.

export var aThing = function( ) {
};
aThing.prototype.sizzle = function( ) {
  console.log( "sizzle!" );
}

I want to add the functionality of an eventemitter library ( https://github.com/Olical/EventEmitter ) to an instance of aThing.

I tried the following:

let MyEventEmitterMixin = (eventEmitter) => class extends eventEmitter {};
export class aThing2 extends mix(aThing).with(MyEventEmitterMixin) {}
var at2 = new aThing2();
at2.sizzle();
//sizzle!
at2.addListener('sizzling', function() {console.log( "zzz";)} );
//Uncaught TypeError: at2.addListener is not a function

So, what I am doing wrong or what should the library be doing differently? Thanks!

Does mixwith detect and cache sub chains?

For example, suppose I have

class Scene extends mix(ImperativeBase).with(TreeNode) {
  // ...
}

class Node extends mix(ImperativeBase).with(TreeNode, Transformable) {
  // ...
}

class SomethingElse extends mix(TreeNode).with(Transformable) {
  // ...
}

Is mixwith.js smart enough to re-use the ImperativeBase -> TreeNode chain between the Scene and Node classes, and the TreeNode -> Transformable chain between the Node and SomethingElse classes?

Compiled version uses ES6 syntax (=>)

Thanks for a nice lib!

Seems like the babel transpilation step is not working correctly, should there really be "=>" in the output file?

./mixwith.js:

const Cached = exports.Cached = mixin => wrap(mixin, superclass => {

Allow the omission of the superclass

Currently the syntax is:

class MyClass extends mix(MySuperClass).with(MyMixin1, MyMixin2) {
}

How about the following?

class MyClass extends mixins(MyMixin1, MyMixin2, MySuperClass) {
}

Details:

  • This better reflects the order of the inheritance chain
  • The last parameter should be optional (you may want to create classes that only assemble mixins and have no superclass)
  • The function could do a simple typecheck by enforcing that the mixins must not have a property prototype, while the superclass (the optional last parameter) must have this property.

Unexpected behavior using rollup

The namespacing introduced through the use of the UMD gulp preset causes unexpected behavior when bundling with rollup (top-level this becoming undefined). Solved by including the module field in package.json to allow rollup to handle es6 imports.

ES3 Implementation

IE6 compatible. Just for fun. Nothing serious. Tell me what you think about this 😄

function Mixin1 (superClass) {
  function Mixin1 () {
    // Passing constructor arguments
    superClass.apply(this, arguments)
  }
  Mixin1.prototype = new superClass()
  // Calling super class method
  Mixin1.prototype.callSuperClassMethodExample = function () {
    superClass.prototype.superClassMethod()
    return this
  }
  return Mixin1
}

function Mixin2 (superClass) {
  function Mixin2 () {
    superClass.apply(this, arguments)
  }
  Mixin2.prototype = new superClass()
  Mixin2.prototype.anotherMixinMethod = function () {
    alert('Hello from mixin!')
  }
  return Mixin2
}

// Composing mixins
function Mixin3 (superClass) {
  return Mixin2(Mixin1(superClass))
}

// Test it
var Foo = (function () {
  function Foo () {
  }
  Foo.prototype.superClassMethod = function () {
    alert('Hello from superClass!')
  }
  return Mixin3(Foo)
}())

new Foo().callSuperClassMethodExample().anotherMixinMethod()

Possible to use bare classes as mixins?

For example,

Suppose I have a class,

class Transformable { ... }

I can easily extend that,

class ThreeDeeObject extends Transformable {}

Now suppose I want ThreeDeeObject to have characteristics of Transformable, EventEmitter, and TreeNode, which let's suppose are all plain classes just like Transformable,

class EventEmitter { ... }
class TreeNode { ... }

Would it be possible to make the mixwith API accept these plain classes as arguments to with(), so that

class ThreeDeeObject extends mix(/* class {} */).with(Transformable, EventEmitter, TreeNode) { ... }

?

The reason I ask is because I also like the option of extending one of those classes directly, not being limited to using them as mixins. For example:

class ThreeDeeObject extends mix(/* class {} */).with(Transformable, EventEmitter, TreeNode) { ... }
class SomethingElse extends EventEmitter { ... } // extend EventEmitter directly.

Extending HTMLElement and Babel

There exist plugins for Babel that work around some issues when transpiling web components. The plugins (e.g. https://github.com/WebReflection/babel-plugin-transform-builtin-classes) wrap classes that extend HTMLElement (or any other native class you care to specify) during transpilation.

However, when using mixwith.js, the fact that a class extends HTMLElement is hidden from the babel plugin at build time since it is passed as a function parameter. Is there any way to use mixwith, while still using the class MyElement extends HTMLElement syntax?

Thanks for the inspiration!

I read your blog post and browsed this repo and it is amazing stuff you have here!
I got really inspired by your work so I ran with it and created this library, loosely based on mixwith:

mics

It looks a lot like yours with some (big) differences:

  • I merged mix and mixin into one
  • with is optional syntax sugar
  • mix can return a mixin instead of a class
  • mixins can be used to instantiate an object directly
  • mix can combine multiple mixins into a new one

I found that creating an actual class is often not desirable because it limits the potential uses for the code. Instead, mics promotes the use of mixins everywhere:

import { mix, is } from 'mics'

var Looker = mix(superclass => class Looker extends superclass {
  look() {
    console.info('Looking good!')
  }
})

// directly instantiate a looker:
var looker = new Looker()
looker.look()

Thanks again for inspiring me and I'd love to get some feedback from the master!

TypeError: m is not a function, when extending a class that uses mixins

It appears that it is not possible to extend a class, which is making use of the mix().

The following illustrates the point (at least when bundling with Webpack, without transpiling)

import { mix } from 'mixwith/src/mixwith';

// A mixin of sorts...
let MyMixin = (superclass) => class extends superclass {

    constructor(){
        super();
        console.log('Mixin');
    }
};

// A class that uses that mixin
class A extends mix(Object).with(MyMixin) {

    constructor(){
        super();
        console.log('Class A');
    }
}

// ...Later, a class that inherits from A
class B extends A {
    constructor(){
        super();
        console.log('Class B');
    }
}

// This fails, ...
let classB = new B();

Location of defect

The issue lies within the MixinBuilder class, in the with method, in that if the superclass is undefined or null, then the method does not handle such exception.

class MixinBuilder {

  constructor(superclass) {
    this.superclass = superclass;
  }

  with() {
    // Here - m might be undefined!
    return Array.from(arguments).reduce((c, m) => m(c), this.superclass);
  }
}

Possible Solution

When editing in the published / bundled source file, I got the above mini-test to work as intended.

class MixinBuilder {

  constructor(superclass) {
    this.superclass = superclass;
  }

  with() {

    return Array.from(arguments).reduce((c, m) => {
      if(typeof m !== "function"){
        return c;
      }

      return m(c);
    }, this.superclass);
  }
}

Please patch as soon as possible

This defect is currently blocking me, because I need to extend a class that I'm not in control of (external dependency), without the usage of mix(). Can you please release a path to this issue, as soon as possible?

instanceof check with multiple mixins

Hi - I'm just exploring this way of implementing mixins and I'm really liking it. However one thing that I've noticed is that the instanceofcheck doesn't work as I expected when using multiple mixins when doing something like:

class D extends mix(A).with(B, C)

In this case D instanceof B will be true, however D instanceof C will be false.

Is there any way to work around this? Or have I misunderstood how the @@ hasInstance support is supposed to work?

Move functionality from MixinBuilder to mixin decorators

Currently MixinBuilder handles the caching of mixin applications and implementation of @@hasInstance which means that mixin consumers only get those features when they use mixwith.js, and not when they use the mixin function directly.

I'd like to move most of the functionality into mixin decorators, so that the author of the mixin adds application caching, @@hasInstance, delegation, de-duping, and possibly trait-like name-collision checks. Users of mixins then get these features no matter how they apply them.

Release compiled / dist version

Awesome library. Just what I was looking for!

Running babel inside node_modules and having library users install transform-es2015-modules-umd should be optional (remove .babelrc from distributed module)

Decorator syntax

Might be nice to allow decorator syntax, so that instead of

class Foo extends mix(SuperClass).with(Foo, Bar) {}

we can

@with(Foo, Bar)
class Foo extends SuperClass {}

But I like better

@Foo @Bar
class Foo extends SuperClass {}

Are either of those decorator forms possible? Can we modify what the class extends (i.e. at first it extends SuperClass, but needs the foo and bar prototypes injected)? Would that be okay to do?

Decorator for super-call?

Calling super.method after your invocation could maybe be abstracted out into an ES2016 decorator?

let MyMixin = (superclass) => class extends superClass {
  @callSuper('before')
  foo(){
    console.log('foo from mixin');
  }
}

const callSuper = (position = 'after') => (target, name, descriptor) => {
  const originalMethod = descriptor.value;
  descriptor.value = () => {
    if (position === 'before' && super[name]) super[name].call(this, arguments);
    originalMethod.call(this, arguments);
    if (position === 'after' && super[name]) super[name].call(this, arguments);
  };
  return descriptor
};

Code is probably wrong, but just to illustrate. Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.