简体   繁体   中英

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?

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
});

As I didn't receive a valid answer, I took my unproductive idea :(

function _loadExternalJS(elem) {
    var scriptRoot = d.getElementById('target_root'),
        scriptElement = d.createElement('script'),
        isElemPlugin = elem.isPlugin || typeof elem.isPlugin === "undefined",
        loadAsync = elem.async || typeof elem.async === "undefined";

    scriptElement.setAttribute('type', 'text/javascript');
    scriptElement.setAttribute('async', loadAsync);

    if (isElemPlugin) {
        scriptElement.onload = function() {
            jsLoaded++;
        };
    }

    scriptElement.setAttribute('src', elem.src);
    scriptRoot.parentNode.insertBefore(scriptElement, scriptRoot.nextSibling);
}

For those wondering about the "then" functionality, I've done the following:

target.init = function (plugins) {
    //do something

    return {
        'then': _then
    }
};

function _then(fn) {
    var loaded;
    loaded = w.setInterval(function () {
        if (jsLoaded === pluginList.length) {
            w.clearInterval(loaded);
            fn();
        }
    }, 10);
}

If any of you have a better idea, please do post !!!! :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM