npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@poppinss/traits

v1.0.1

Published

Dead simple traits for ES6 classes

Downloads

478

Readme

Traits

A tiny library to add support for Traits in Javascript and Typescript projects.

circleci-image typescript-image npm-image license-image

This library enables the support for using traits in your Javascript or Typescript codebase.

Table of contents

Inheritance in Javascript

Since Javascript only allows extending a single class at a time, it can become harder to re-use code which relies on many concerns.

The Javascript inheritance model follows a vertical approach, so inheritance leads to classes that stack upon one another as layers of a cake. For example:

class Person {
}

// These are concerns
class Runner {}
class Walker {}

class Walker extends Person {}
class Runner extends Walker {}

class User extends Runner {}

As you can notice, in order to add capabilities of a Runner and a Walker to a User, you have to stack extend calls on top of each other and have a nested __proto__ to look for properties.

Why not mixins?

Mixins follows the same approach of stacking extend calls on top of each other with using a function to do that instead. For example:

function Runner (Base) {
  return class Runner extends Base {}
}

function Walker (Base) {
  return class Walker extends Base {}
}

This is infact suggested by many in the Typescript community.

Using Traits

Traits follows a simple approach of copying the members of a trait to the destination class, which means after a trait is applied, that class will be garbage collected right away and you are always working with the destination class object.

Also, this library enforces some constraints to keep the code simpler and easy to reason about.

  • Only methods are allowed: Traits cannot define it's own state or properties that are not methods. If a trait needs some state to operate, then it can enforce the parent class to define that.
  • Getters/setters are allowed: Getters & setters are allowed, since they operate on a pre-existing state.
  • Static properties are allowed: We will copy the static properties to the destination class.
  • Prototype properties are allowed: We will copy the prototype properties to the destination class.

Installation

Install the package from npm registry as follows:

npm i @poppinss/traits

# yarn
yarn add @poppinss/traits

Usage

Using as a decorator

import { trait } from '@poppinss/traits'

class Runner {
  run () {
    console.log(this instanceof User)
  }
}

class Walker {
  walk () {
    console.log(this instanceof User)
  }
}

class Person {}

@trait(Runner)
@trait(Walker)
export class User extends Person {}

The trait decorator will copy the run and walk methods to the User class and this inside those methods will point towards the User class instance.

Using as a method

If you are not using decorators, then you can make use of the applyTraits method instead.

import { applyTraits } from '@poppinss/traits'

class User extends Person {}

applyTraits(User, [Runner, Walker])
module.exports = User

Constraints

As mentioned earlier, the traits cannot define state as it will result in an error.

class Runner {
  public username = 'virk'
  public run () {
    return `${this.username} runs`
  }
}

When using Runner as a trait, a runtime exception will be raised that username is not allowed to be set. Instead, you can enforce the consumer of trait to define the username. Doing this in Typescript is even simpler with compile time feedback.

export class Runner {
  /**
   * It is ok to have uninitialized properties for the
   * typescript compiler to work. But assigning them
   * a value is not allowed
   */
  public username: string
  public run () {
    return `${this.username} runs`
  }
}

type Constructor<T> = { new (): T }
export type ConstructorStatic<T = Constructor<{ username: string }>> = T

Usage

import { trait } from '@poppinss/traits'
import { Runner, ConstructorStatic } from './Runner'

@trait<ConstructorStatic>(Runner)
class User {}

Compile time error

Fix it by defining it inside the User class.

@trait<ConstructorStatic>(Runner)
class User {
  public username: string
}

Extending Typescript types

Unfortunately, Typescript doesn't allow decorators to extend the types of the source class and hence you will have to manually extend the types of the traits you are using. For example:

@trait(Runner)
class UserBase {
  public username: string
}

const User = UserBase as unknown as (typeof UserBase & typeof Runner) & {
  new (): UserBase & Runner,
}

export default User

after this, the run method will show up on the User class as well.

Another option is to make use of interface with the same name as the class

interface User {
  run (): string
  username: string
}

@trait(Runner)
class User {
  public username: string
}