Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript es5 async plugin architecture

I'm trying to figure out a way to structure a new framework for work capable of injecting plugins. The idea is to have each file be loaded asynchronously.

Here is how I would love to configure my plugins:

<script id="target_root" src="assets/js/target/target.js" async="true"></script>
<script>
    var target = target || {};
    target.cmd = target.cmd || [];

    target.cmd.push(function () {
        target.loadPlugins([
            {"name": "root", "src": "assets/js/target/target.root.js"},
            {"name": "krux", "src": "assets/js/target/target.krux.js"}
        ]).then(
            target.init([
                {
                    'plugin': 'root',
                    'opts': {
                        'foo': 'bar'
                    }
                },
                {
                    'plugin': 'krux',
                    'opts': {
                        'foo': 'bar'
                    }
                }
            ])
        )
    });
</script>

As I'd be using inline functions (within the DOM) I thought of using a command queue which on load would invoke all pushed functions (a bit like the googletag cmd of DFP).

As stated before each plugin would be loaded asynchronously so the initialization of each of them should only start when all of them are loaded (hence the then() function).

Here you have my script:

var target = (function(root, w, d, c) {
    var queueIndex = 0,
        amountPluginsLoaded = 0,
        pluginsLoaded = [];

    root.cmd = {
        'queue': root && root.cmd ? root.cmd : [],
        'push': function(fn) {
            this.queue.push(fn);
            this.next();
        },
        'next': function() {
            if (this.queue.length > 0) {
                this.queue.shift()();
            }
        }
    };

    root.init = function(plugins) {

    };

    root.loadPlugins = function(plugins) {
        var i = 0,
            len = plugins.length;
        for(; i < len; i++) {
            _loadExternalJS(plugins[i]);
        }
    };

    function _loadExternalJS(plugin) {
        var scriptRoot = d.getElementById('target_root'),
            scriptElement = d.createElement('script');

        scriptElement.setAttribute('type', 'text/javascript');
        scriptElement.setAttribute('async', 'true');
        scriptElement.onload = function() {
            amountPluginsLoaded++;
            pluginsLoaded.push(plugin.name);
        };
        scriptElement.setAttribute('src', plugin.src);
        scriptRoot.parentNode.insertBefore(scriptElement, scriptRoot.nextSibling);
    }

    function _initPlugin(plugin) {

    }

    for (; queueIndex < root.cmd.queue.length; queueIndex++) {
        root.cmd.next();
    }
}(target || {}, window, document, console));

Here you have the basic cmd functionality which would be overridden and the loading of each of the scripts.

What I can't seem to figure is how to fire up the then(). I suppose you'd keep track of it in the _loadExternalJS() in it's onload event (as you can see in the code). But Simply adding an if(amountPluginsLoaded === pluginsLoaded.length) { fire all inits } seems unproductive and not something that belongs in the function. this is why I'd love to implement some then() feature.

Any ideas/opinions?

like image 883
Goowik Avatar asked Jun 23 '17 14:06

Goowik


Video Answer


1 Answers

You could use promise and promise.all to check all of them are loaded.

root.loadPlugins = function(plugins) {
    var promiseArray = plugins.map(function(plugin){
         return _loadExternalJS(plugin);
    });
    return Promise.all(promiseArray);
};

function _loadExternalJS(plugin) {
    return new Promise((resolve, reject) => {
       var scriptRoot = d.getElementById('target_root'),
           scriptElement = d.createElement('script');

       scriptElement.setAttribute('type', 'text/javascript');
       scriptElement.setAttribute('async', 'true');
       scriptElement.onload = function() {
          amountPluginsLoaded++;
          pluginsLoaded.push(plugin.name);
          resolve(plugin.name);
       };
       scriptElement.setAttribute('src', plugin.src);
       scriptRoot.parentNode.insertBefore(scriptElement, scriptRoot.nextSibling);
   });
}

then

root.loadPlugins().then(function(){
    //initialize plugins
});
like image 195
fingerpich Avatar answered Oct 29 '22 06:10

fingerpich