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

pluginize

v0.1.3

Published

The infrastructure package for creating your library / project.

Downloads

9

Readme

pluginize

The infrastructure package for creating your library / project.

npm version npm downloads npm downloads

Quick Links

Install

    npm install pluginize --save

The Why

Just write this line of code

const yourLibrary = pluginize(yourconfig);

and you will have

  • the ability to add Plugins
yourLibrary.run({
    plugins:[pluginA, pluginB,..]
})
  • the ability to add Plugins that can be built of other plugins
const plugin = {
    // add pluginconfiguration here
    plugins: [nestedPlugin1,nestedPlugin2];
}
yourLibrary.run({
    plugins:[plugin]
});
  • add custom properties / methods to your library
const yourLibrary = pluginize({
    onInit(config,pluginConfig){
        return{
            hello(name){
                console.log('hello' + name || pluginConfig.defaultName);
            }
        }
    }
})
  • run your library asynchronous
const yourLibrary = pluginize({
    //some config
});
const result = await yourLibrary.runPromise();
  • the chance to hook in every process of your library (and of course of the plugins, too)
yourLibrary.runPromise({
    onInitPlugin(config){
        //do sth when your library / the plugins are initialized
    },
    onPluginsInitialized(){
        // do sth when all plugins are initialized
    }
    onInit(){
        // do sth on init
    }
    ...
});
  • choose what your plugin returns
//default: a library returns an object (the context) - but you can modify it
const yourlibrary = pluginize({
    rename: {
        // renames the keys of the output
    }
    delete: {
        // removes the keys of the output
    },
    return: 'akey', //if you want a specific key to be returned
    onReturn(context){
        //when you want sth completely different, for example a function, you can do it here :)
    }
});

//now returns whatever you want instead of the default output
const output yourLibrary.run(config);
  • add your hooks that can be used
//default: a library returns an object (the context) - but you can modify it
const yourlibrary = pluginize({
    onInit(){
        setTimeout(function(config,ctx){
            ctx.after5Seconds.call('5 seconds later');
        }, 5000);

        return {
            after5Seconds: pluginize.SyncHook() // don't worry about the hooks - your will learn more about them later
        }
    }
});

//it can be used like this
yourLibrary.runPromise({
    after5Seconds(message){
        //do sth after 5 seconds
    }
})

//now returns whatever you want instead of the default output
const output = yourLibrary.runPromise(config);

...

##Step by step

Let's create a library together, step by step. So you will learn all features of pluginize.

Preparation

View some examples

Get the package

    npm install pluginize --save

and import it into your project

import {pluginize} form 'pluginize';

or as a script

<!-- you can find the files in dist/pluginize.min.js" in this repository>-->
<script src="path/to/pluginize.min.js"></script>

As a first step we create our first library that does (almost) nothing.

const myLibrary = pluginize();

//yippie, we have a default result from a syncronous task
const syncResult = myLibrary.run();

//if we want to run some async tasks, we can use runPromise()
const asyncResult = await myLibrary.runPromise();

Both results will look similar to this

{
    plugins: [ /*some internal plugins*/  ],
    config: { name: 'Pluginize' },
    _context: true,
    addPlugin: [Function],
    onReturn: SyncHook {  },
    onPreInitPlugin: SyncWaterfallHook {},
    onPluginsInitialized: SyncHook {  },
    onInitPlugin: SyncHook { }
    log: [Function], //you can log sth with result.log(xxx)
    disableKeyCheck: [Function], 
    on: [Function] // you can listen to hooks with result.on()
}

Yippie we have built our first library. But it does not do what we want. Let's change it.

Add custom functions

View an example

Of course your library will need some functions that the users can use. Let's add some. We will create a small Mathlibrary with useful helperfunctions.

const myLibrary = pluginize({
    name: "MathLibrary",
    onInit(config, pluginConfig, context) {
        //1st way to add sth in the context - modify the context object
        context.add = function(a,b) {
            return a + b;
        }

        context.pluginname = config.name;

        //2nd way: every attribute returned will be added to the context
        return {
            multiply(a,b) {
                return a * b;
            }
        }
    }
});

//now our result includes these two functions
const result = myLibrary.run({name: 'MathLibrary'});
result.add(1,2); // = 3
result.multiply(1,2); // = 2
result.pluginname; // = MathLibrary

Add Plugins

View an example

This is such a great feature - others should also be able to use it. Let's outsource it as a plugin.

//math.plugin.js
module.exports = {
    // Every plugin needs a name - so let's name it 'MathLibraryPlugin'
    name: 'MathLibraryPlugin',
    onInit(config, pluginConfig,context) {
        context.add = function(a,b) {
            return a + b;
        }

        context.pluginname = config.name;

        return {
            multiply(a,b) {
                return a * b;
            }
        }
    }
}
// index.js
const MathLibraryPlugin = require('./math.plugin');
const { pluginize } = require('pluginize');

const myLibrary = pluginize({
    plugins: [MathLibraryPlugin]
});

const result = myLibrary.run();
result.add(1,2); // = 3
result.multiply(1,2); // = 2
result.pluginname; // = MathLibrary

Hint: it is recommended to write Plugins as a function that returns a config - so users can customize your plugin

//sayhello.plugin.js
module.exports = function(customConfig={}){
    return {
        name: 'MathLibraryPlugin-Customconfig',
        onInit(config, pluginConfig,context) {
            // for a better readability we initialized all in the return value. But it is still valid like written above. 
            return {
                pluginname: customConfig.namePrefix + config.name,
                add(a,b){
                    return a + b;
                },
                multiply(a,b) {
                    return a * b;
                }
            }
        }
    }
}
// index.js
const MathLibraryPlugin = require('./math.plugin');
const { pluginize } = require('pluginize');

const myLibrary = pluginize({
    //now you call the plugin as a function
    plugins: [ MathLibraryPlugin({namePrefix:'the real '}) ]
});

const result = myLibrary.run();
result.add(1,2); // = 3
result.multiply(1,2); // = 2
result.pluginname; // = the real MathLibrary

Custom Keys

View an example

Now we want to add some customized beavior. Let's add custom data to the context, that the user can add via config.

Example: A user can add custom data via attribute "custom".

// this is the plugin - it adds the key "custom" to the context. 
const customKeyPlugin = {
    name: "CustomKeyPlugin",
    onInit(config) {
        return {
            custom: config.custom
        }
    },
};

// the user adds a custom function to the custom context
const myLibrary = pluginize({
    custom: function(){/*a user defined function*/},
    plugins: [customKeyPlugin]
})
const result = myLibrary.run();
/*
    result should be: {
        ...
            custom: function(){/*the user defined function*/},
        ...
    }
*/

But wait... there is an error:

Config attribute "custom" is used but not allowed. Allowed are ...

By Default just a few keys are allowed via for the config - we must whitelist new ones.

Recommended way

Let's allow the attribute 'custom';

const customKeyPlugin = {
    allowKeys: ['custom'],
    name: "CustomKeyPlugin",
    onInit(config) {
        return {
            custom: config.custom
        }
    },
};

Now the plugin works as expected.

:warning: Prooving Nested keys is not (yet) supported.

// this does not work you have to validate these keys by yourself
{ 
    allowKeys['custom.a','custom.b']
}

Additional way

(not recommended, but possible) disableKeycheck. If you're bored of adding keys again and again, you can disable this check. Then the user can add anything to the config without any error that is thrown.

const customKeyPlugin = {
    //disables the keycheck for ALL keys.
    disableKeyCheck: true,
    name: "CustomKeyPlugin",
    onInit(config) {
        return {
            custom: config.custom
        }
    },
};

Change Return value

View an example

Right now pluginize().run() returns the whole context. You probably won't need the whole context but just a part of it. There are a few ways of changing the content:

Therefore we will use an advanced version of the MathLibraryPlugin - the CalculationPlugin. It sums up the numbers entered on config-attribute "numbers" and writes it into the context-attribute "sum".

const CalculationPlugin = {
    allowKeys: ['numbers'],
    name: 'CalculationPlugin',
    onInit(config) {
        return {
            //this will sum up numbers. example: [1,2,3,4] => 10
            sum: config.numbers.reduce((pv, cv) => pv + cv, 0)
        }
    }
};

Default return value

When we just use this plugin, pluginize().run() will return the whole context

const myLibrary = pluginize({
    numbers: [1,2,3,4,5,6],
    plugins: [CalculationPlugin]
})
const result = myLibrary.run();
/*
    result: {
        // the whole context
        ...
            sum: 21,
        ...
    }
*/

Return Key

Now we want to return a specific key of the context, not the whole context anymore - in this case the key "sum".

const myLibrary = pluginize({
    return: 'sum',
    numbers: [1,2,3,4,5,6],
    plugins: [CalculationPlugin]
})
const result = myLibrary.run();
/*
    result is: 21
*/

Rename Key

Maybe we want the whole context - but the key "sum" should have another name: "newSum":

const myLibrary = pluginize({
    rename: {
        sum: 'newSum'
    },
    numbers: [1,2,3,4,5,6],
    plugins: [CalculationPlugin]
})
const result = myLibrary.run();
/*
    result: {
        // the whole context
        ...
            newSum: 21,
        ...
    }
*/

Clone Key

Sometimes it is good to have a copy of a key (to modify the 2nd one just a little bit) - therefore you can use the key "clone".

const myLibrary = pluginize({
    clone: {
        sum: 'newSum'
    },
    numbers: [1,2,3,4,5,6],
    plugins: [CalculationPlugin]
})
const result = myLibrary.run();
/*
    result: {
        // the whole context
        ...
            sum: 21,
            newSum: 21,
        ...
    }
*/

Delete Key

To delete a key from the context.

const myLibrary = pluginize({
    delete:  ['sum'],
    numbers: [1,2,3,4,5,6],
    plugins: [CalculationPlugin]
})
const result = myLibrary.run();
/*
    result: {
        // the whole context
        ...
            // just the default keys,
            // sum does not exist in the context anymore
        ...
    }
*/

Use hooks

Hooks help us to add code in a specific situation. By convention all hooks should start with "on" - except there is a good reason to do it different.

We already got in contact with hooks, do you remember?

const CalculationPlugin = {
    allowKeys: ['numbers'],
    name: 'CalculationPlugin',
    // this is one hook
    onInit(config) {}
};

There are some more Hooks already defined by pluginize - but you can also add some. These hooks already exist by default: | Hook | Called | When to use | | --- | --- | --- | | onPreInit | before config is analyzed| When your inputconfig does not match the criteria, you can modify it here | onInit | onInit, after onPreInit | To setup hooks, Context,... | |onPreInitPlugin| before a plugin is executed | When your pluginconfig does not match the criteria, you can modify it here| |onInitPlugin|when a plugin is executed | If you have logic depending on the configuration of the plugins, add it here |onPluginsInitialized|When all plugins are executed| do something after all Plugins finished | |onReturn|before returning the result|to modify the returned result|

Details about the hooks you can read here.

Let's use this knowledge to create a small Feature-Toggle-Library. View an example

What it should be able to do:

const result = featureToggleLib.run({
    featurea: true,
    featureb: false,
    featurec: true
    ...
});
result.isActive('featurea'); //true

First, let's create a FeatureTogglePlugin

const FeatureTogglePlugin = {
    name: "FeatureTogglePlugin",
    /*
        Here we map the config{
            featurea: true,
            featureb: false,
            featurec: true
        }
        to {
            data: {
                featurea: true,
                featureb: false,
                featurec: true
            }
        } 
    */
    onPreInit(config) {
        return {
            data: config
        }
    },
    
    // Here we define the library that will be returned
    onInit(config) {
        return {
            featureToggle: {
                data: config.data,
                isActive: function(key) { return config.data[key] }
            }
        }
    },
    // Returns key featureToggle - we define it in onInit()
    return: 'featureToggle',
    // we must allow key "data" because we use it in hook onPreInit()
    allowKeys: ['data'],
};

Now we will use this plugin

const featureToggle = pluginize({
    name: 'FeatureToggle',
    debug: true,

    plugins: [FeatureTogglePlugin]
});

And voilà, now we have a small featureToggle-Library.

/**
 * Will return an object {
 *  data: {
 *      featurea: true,
 *      featureb: false,
 *      featurec: true
 *  },
 *  isActive: function(){logic inside}
 * }
 */
const result = featureToggle({
    featurea: true,
    featureb: false,
    featurec: true
}).run();

Create your own hooks

When your Library becomes more complex, you may need your own hooks. Luckily it is very easy to create new Hooks.

Plugin Livecycle