Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
The ts-mixer package is a TypeScript library that provides utilities for creating mixins and multiple inheritance in TypeScript. It allows developers to combine multiple classes into one, enabling more flexible and reusable code structures.
Basic Mixin
This feature allows you to create a new class that combines the methods and properties of multiple classes. In this example, class C inherits methods from both class A and class B.
const { Mixin } = require('ts-mixer');
class A {
methodA() {
console.log('Method A');
}
}
class B {
methodB() {
console.log('Method B');
}
}
class C extends Mixin(A, B) {}
const c = new C();
c.methodA(); // Method A
c.methodB(); // Method B
Advanced Mixin with Constructor
This feature demonstrates how to handle constructors when mixing classes. The new class C can initialize properties from both class A and class B.
const { Mixin } = require('ts-mixer');
class A {
constructor(public name: string) {}
methodA() {
console.log(`Method A: ${this.name}`);
}
}
class B {
constructor(public age: number) {}
methodB() {
console.log(`Method B: ${this.age}`);
}
}
class C extends Mixin(A, B) {
constructor(name: string, age: number) {
super(name, age);
}
}
const c = new C('John', 30);
c.methodA(); // Method A: John
c.methodB(); // Method B: 30
Mixin with Interfaces
This feature shows how to use mixins with interfaces. Class C implements both IA and IB interfaces and inherits methods from class A and class B.
const { Mixin } = require('ts-mixer');
interface IA {
methodA(): void;
}
interface IB {
methodB(): void;
}
class A implements IA {
methodA() {
console.log('Method A');
}
}
class B implements IB {
methodB() {
console.log('Method B');
}
}
class C extends Mixin(A, B) implements IA, IB {}
const c = new C();
c.methodA(); // Method A
c.methodB(); // Method B
The mixwith package provides a similar functionality for creating mixins in JavaScript and TypeScript. It offers a more flexible and modern approach to mixins, but ts-mixer is more TypeScript-centric and provides better type safety.
The mixin-deep package allows deep merging of objects and mixins. While it is more focused on object merging rather than class-based mixins, it can be used to achieve similar results in a different way.
Lodash is a utility library that provides a wide range of functions, including mixin capabilities. However, it is more general-purpose and not specifically designed for TypeScript or class-based mixins.
It seems that no one has been able to provide an acceptable way to gracefully implement the mixin pattern with TypeScript. Mixins as described by the TypeScript docs are far less than ideal. Countless online threads feature half-working snippets. Some are elegant, but fail to work properly with static properties. Others solve static properties, but they don't work well with generics. Some are memory-optimized, but force you to write the mixins in an awkward, cumbersome format.
My fruitless search has led me to believe that there is no perfect solution with the current state of TypeScript. Instead, I present a "tolerable" solution that attempts to take the best from the many different implementations while mitigating their flaws as much as possible.
"target": "es6"
in your
tsconfig.json
) will likely cause issues since ES6 doesn't allow you to call constructor
functions without the new
keyword, which is crucial for mixins to work at all. If you
must use ES6, you must define your classes "the old way" rather than with the new class
keyword to avoid runtime errors.Mixin<A & B>(A, B)
in
order for the types to work correctly. ts-mixer is able to infer these types, so you can
just do Mixin(A, B)
, except when generics are involved. See
Dealing with Generics.instanceof
support; Because this library is intended for use with TypeScript, running
an instanceof
check is generally not needed. Additionally, adding support can have
negative effects on performance. See the
MDN documentation
for more information.npm i --save ts-mixer
If you're looking for more complete documentation, go here. If you just need a few tips to get started, keep reading.
import {Mixin} from 'ts-mixer';
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class RunnerMixin {
protected runSpeed: number = 10;
public run(){
console.log('They are running at', this.runSpeed, 'ft/sec');
}
}
class JumperMixin {
protected jumpHeight: number = 3;
public jump(){
console.log('They are jumping', this.jumpHeight, 'ft in the air');
}
}
class LongJumper extends Mixin(Person, RunnerMixin, JumperMixin) {
public longJump() {
console.log(this.name, 'is stepping up to the event.');
this.run();
this.jump();
console.log('They landed', this.runSpeed * this.jumpHeight, 'ft from the start!');
}
}
Consider the following scenario:
import {Mixin} from 'ts-mixer';
class Person {
public static TOTAL: number = 0;
constructor() {
(<typeof Person>this.constructor).TOTAL ++;
}
}
class StudentMixin {
public study() { console.log('I am studying so hard') }
}
class CollegeStudent extends Mixin(Person, StudentMixin) {}
It would be expected that class CollegeStudent
should have the property TOTAL
since
CollegeStudent
inherits from Person
. The Mixin
function properly sets up the
inheritance of this static property, so that modifying it on the CollegeStudent
class
will also affect the Person
class:
let p1 = new Person();
let cs1 = new CollegeStudent();
Person.TOTAL === 2; // true
CollegeStudent.TOTAL === 2; // true
The only issue is that due to the impossibility of specifying properties on a constructor type, you must use some type assertions to keep the TypeScript compiler from complaining:
CollegeStudent.TOTAL ++; // error
(<any>CollegeStudent).TOTAL ++; // ok
(<typeof Person><unknown>CollegeStudent).TOTAL++; // ugly, but better
Normally, the Mixin
function is able to figure out the class types and produce an
appropriately typed result. However, when generics are involved, the Mixin
function
is not able to correctly infer the type parameters. Consider the following:
import {Mixin} from 'ts-mixer';
class GenClassA<T> {
methodA(input: T) {}
}
class GenClassB<T> {
methodB(input: T) {}
}
Now let's say that we want to mix these two generic classes together, like so:
class Mixed extends Mixin(GenClassA, GenClassB) {}
But we run into trouble here because we can't pass our type parameters along with the
arguments to the Mixin
function. How can we resolve this?
One solution is to pass your type information as type parameters to the Mixin
function
(note that the string
and number
types are arbitrary):
class Mixed extends Mixin<GenClassA<string>, GenClassB<number>>(GenClassA, GenClassB) {}
This really works quite well. However, it gets worse if you need the mixins to reference type parameters on the class, because this won't work:
class Mixed<A, B> extends Mixin<GenClassA<A>, GenClassB<B>>(GenClassA, GenClassB) {}
// Error: TS2562: Base class expressions cannot reference class type parameters.
To solve this issue, we can make simultaneous use of class decorators and interface merging to create the proper class typing. It has the benefit of working without wrapping the class in a function, but because it depends on class decorators, the solution may not last for future versions of TypeScript. (I tested on 3.1.3)
Either way, it's a super cool solution. Consider the following:
import {MixinDecorator} from 'ts-mixer';
@MixinDecorator(GenClassA, GenClassB)
class Mixed<A, B> {
someAdditonalMethod(input1: A, input2: B) {}
}
The first thing to note is the MixinDecorator
import. This function is very similar to
the Mixin
function, but in a decorator
format. Decorators have the annoying property that even though they may modify the shape
of the class they decorate "on the JavaScript side," the types don't update "on the
TypeScript side." So as far as the TypeScript compiler is concerned in the example above,
class Mixed
just has that one method, even though the decorator is really adding methods
from the mixed generic classes.
How do we convince TypeScript that Mixed
has the additional methods? An attempt at a
solution might look like this:
@MixinDecorator(GenClassA, GenClassB)
class Mixed<A, B> implements GenClassA<A>, GenClassB<B> {
someAdditonalMethod(input1: A, input2: B) {}
}
But now TypeScript will complain that Mixed
doesn't implement GenClassA
and GenClassB
correctly, because it can't see the changes made by the decorator. Instead, we can use
interface merging:
@MixinDecorator(GenClassA, GenClassB)
class Mixed<A, B> {
someAdditonalMethod(input1: A, input2: B) {}
}
interface Mixed<A, B> extends GenClassA<A>, GenClassB<B> {}
TADA! We now have a truly generic class that uses generic mixins!
It's worth noting however that it's only through the combination of TypeScript's failure to consider type modifications with decorators in conjunction with interface merging that this works. If we attempted interface merging without the decorator, we would run into trouble:
interface Mixed<A, B> extends GenClassA<A>, GenClassB<B> {}
class Mixed<A, B> extends Mixin(GenClassA, GenClassB) {
newMethod(a: A, b: B) {}
}
// Error:TS2320: Interface 'Mixed<A, B>' cannot simultaneously extend types 'GenClassA<{}> & GenClassB<{}>' and 'GenClassA<A>'.
// Named property 'methodA' of types 'GenClassA<{}> & GenClassB<{}>' and 'GenClassA<A>' are not identical.
We get this error because when the Mixin
function is used in an extends clause, TypeScript
is smart enough extract type information, which conflicts with the interface definition
above it; when Mixin
is given the generic classes as arguments, it doesn't receive their
type parameters and they default to {}
. Even if you try to // @ts-ignore
ignore it,
the type checker will prefer the types of the Mixin
function over those of the interface.
All contributions are welcome! To get started, simply fork and clone the repo, run
npm install
, and get to work. Once you have something you'd like to contribute,
be sure to run npm run lint && npm run test
locally, then submit a PR.
Tests are very important to consider and I will not accept any PRs that are poorly tested. Keep the following in mind:
*.test.ts
file in the test
directory, so that all of
the nuances of the feature can be adequately covered.Tanner Nielsen tannerntannern@gmail.com
FAQs
A very small TypeScript library that provides tolerable Mixin functionality.
The npm package ts-mixer receives a total of 476,569 weekly downloads. As such, ts-mixer popularity was classified as popular.
We found that ts-mixer demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.