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

decorative

v0.1.6

Published

decorative.js is an ES7 micro-framework promoting @decorator pattern-driven app development.

Downloads

10

Readme

decorative.js

...is an ES7 micro-framework promoting @decorator pattern-driven app development.

Using decorators means to write:

  • less lines
  • less complex
  • better maintainable
  • better readable
  • more loosely coupled code.

It's really "decorative" code you will be writing using @decorators ;)

Decorators were introduced to Babel.js in spring 2015 by Yehuda Katz. Since then, only a few decorators were implemented (see core-decorators by jayphelps). But the world didn't care much, no matter if they are amazing or not.

When I discovered the es7.decorators I realized the power they could bring to my web apps and I began to implement decorative.js.

decorative.js is currently under development but already used to develop a web app which I will release soon. I rely on this framework and it's toolchain (Babel.js) and it works stable for my use-cases. I can't guarantee that it fully works for you but I think it's worth a try or at least a quick look.

Decorative app development

decorative.js heavily relies on ES6 and 7. If you aren't already familiar with the great language features introduced in 6 right now, I kindly recommend you to read on here:

https://github.com/lukehoban/es6features

Well, decorators are part of the ES7 proposals. Let's see, how it looks like to use them.

Using decorators

First, we import decorative and the decorators we are planning to use. Afterwards we decorate classes and class members like this:

import { Singleton } from 'decorative';

@Singleton()
class MyFirstDecoratedClass {

    hello = "Hi, ";   

    sayHello() {
        console.log(this.hello + "I'm a singleton!");
    }
}

Guess what? It's really a singleton now!

MyFirstDecoratedClass.sayHello();

Well, now that you got picture: Lets look what we need to run this code in today's JS engines.

Requirements and setup

Just install this package like this:

npm install decorative --save

If you also plan to use it with React, you may want to install decorative-react:

npm install decorative-react --save

To use decorative you need to cross-compile the code from tomorrows ECMAScript 7 code back to today's ECMAScript 5 / 6 code. Well, this is quite easy. We use Babel.js and grunt for this. (while gulp is compatible too, I don't have a configuration example yet; feel free to provide one :)

So lets write a Grunt file and include webpack like this:

grunt.initConfig({ 
    webpack: {
        app: {
            entry: "$yourAppEntry",
            output: {
                path: '$yourGeneratedOutputDirectory',
                filename: "$nameOfTheSingleOutputFileBabelJSGeneratesForYou.js"
            },
            devtool: 'source-map', // <- so you can debug ECMAScript 7 code in devTools today
            module: {
                loaders: [
                    {
                        test: /\.js$/,
                        loader: "babel-loader"
                    }
                ]
            },
            watch: true,          // <- re-compile on JS source/app code change
            keepalive: true,      // <- start the webpack dev-server
            failOnError: false    // <- prevents stopping the dev-server on error
        }
    }
});

A working example of a Gruntfile can be found in this repositories "Gruntfile.js".

Additionally create a .babelrc file in your projects root directory (next to Gruntfile.js) and put it like:

{
  "presets": ["es2015", "stage-0"],
  "plugins": ["transform-decorators", "syntax-class-properties"]
}

This configures Babel.js to parse/transform the full spectrum of ES6+ features.

Now that we set up Babel.js and grunt, we can run the transpiling process continuously:

grunt webpack

(Or just "grunt" when you set "webpack" as a default task)

Decorators

Now that the cross-compilation works and you're able to use decorative and it's decorators, lets take a look which decorators have been implemented and are ready to use:

@Singleton()

Creates an instance of the decorated class by calling it's constructor.

  import { Singleton } from 'decorative';
  
  @Singleton()
  class MyFirstDecoratedClass {
      
      hello = "Hi";   
  
      sayHello() {
          console.log("${this.hello}, I'm a singleton!");
      }
  }

@Inject(valueOrClassName:Object [,instantiate:Boolean])

Assigns any object to the decorated class property. May use/create a singleton instance of a class.

import { Singleton, Inject } from 'decorative';
  
@Singleton()
class UsingInject {
      
      @Inject('MyFirstDecoratedClass')
      myFirstDecoratedClass = null;
}

UsingInject.myFirstDecoratedClass.sayHello();

@Translate(translations:Object [, locale:String=en_GB])

Injects the provided i18n translation object into the target class prototype. If a locale is given, it maps like translations[locale]:

import { Translate, Singleton } from 'decorative';
import i18n from 'i18n/login';

@Translate(i18n, 'de_DE')
@Singleton
class LoginForm {

    get loginButtonLabel() {
        return this.i18n.loginButtonLabel;
    }
    
    getUserWelcomeMsg(name) {
        return this.i18n.userWelcomeMsg(name);
    }
}

You can use it like:

// if you like getters :)
LoginForm.loginButtonLabel()                 // "Einloggen"
LoginForm.getUserWelcomeMsg('John Doe')      // "Herzlich Willkommen, John Doe!"

// without getters
LoginForm.i18n.loginButtonLabel              // "Einloggen"
LoginForm.i18n.userWelcomeMsg('John Doe')    // "Herzlich Willkommen, John Doe!"

Well, the i18n/login.js should look like this:

export default {
    de_DE: {
        loginButtonLabel: 'Einloggen',
        userWelcomeMsg: (name) => return "Herzlich Willkommen, ${name}!"
    },
    en_GB: {
        loginButtonLabel: 'Login',
        userWelcomeMsg: (name) => return "Welcome, ${name}!"
    }
}

@App(startConfig:Object)

Creates an application instance (e.g. delayed, see below) and emits appStarted on the internal event bus. Modules will create their instances afterwards. (see @AppModule).

An optional onStart() lifecycle method gets called (if impl.) on the app class right before the modules get started.

The startConfig-object of @App supports the following properties:

delayed:Function This function receives a callback function as it's first argument. Call it at any time to create the App instance right at the time you call that start function. This also delays the startup of the @AppModules for sure.

import { App } from 'decorative';

// module imports should happen before App declaration
import './modules/Login';
import './modules/Dashboard';

@App({
    delayed: (start:Function) => {
        $(document).ready(() => {
            start();
        });
    }
})
class MyApp {

}

Limitation: Module imports should happen before App declaration.

@AppModule()

Automatically creates an instance of the decorated class and adds it to the application instance. Afterwards it emits moduleStarted in the internal event bus so that @Route() decorators get activated in time.

import { AppModule } from 'decorative';
    
@AppModule()
class Login {

    constructor() {
        console.log("I'm a module of ${this.app}!");
    }
}

@Route(route:String [,authCb(isAllowed:Function):Function])

Watches for the module classes to be started and calls the decorated method as soon a matching document.location route gets detected.

import { AppModule, Route } from 'decorative';

@AppModule()
class Login {

    @Route('/')
    showLoginView() {
        // show the login view here (e.g. using decorative-react ;)
    }
}

As you can see, the @Route() decorator may be called with a second argument authCb. This enabled flexible, (even asynchronous) authentication checks:

@Route('/', isAllowed => {
    // check if "/" is allowed, then call:
    isAllowed(true); // or false, to deny
})

The method gets called only if isAllowed gets called.

Limitation: Can be applied to methods in @AppModule decorated classes only yet. (see roadmap)

@Promised()

Wraps a decorated method inside a Promise.

@Promised()
findMyPhone(howItLooksLike, foundCb, imSureSomeoneHasStolenItCb) {

    $.ajax({
        url: '/whereIsMyPhone',
        data: howItLooksLike
    })
    .then(found);
    .catch(imSureSomeoneHasStolenIt);
}

The magic here is, that the method's return value is a promise auto-magically:

.findMySocks({color: 'white', type: 'android'})
    .then(myPhone => {
        // alright
    })
    .catch(err => {
        this.comingLateToday();
    });

Roadmap

Although a lot has been implemented, there is still a lot to do and you are very welcome to push :)

Features planned to be introduced in the near future:

  • @Singleton should support constructor arguments
  • @Inject should be able to consume and resolve a full-featured DI config object as first argument
  • @Route should support to be applied on non-@StartModule classes (and therefore auto-detect if the class it belongs to is a module)
  • Please create an issue regarding your own feature requests