pluginize

0.1.3 • Public • Published

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.

⚠️ 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

Package Sidebar

Install

npm i pluginize

Weekly Downloads

0

Version

0.1.3

License

MIT

Unpacked Size

682 kB

Total Files

48

Last publish

Collaborators

  • manu_g