Source: resources/script.js

Object.assign(pc, function () {
    'use strict';

    /**
     * @constructor
     * @name pc.ScriptHandler
     * @classdesc ResourceHandler for loading JavaScript files dynamically
     * Two types of JavaScript files can be loaded, PlayCanvas scripts which contain calls to {@link pc.createScript},
     * or regular JavaScript files, such as third-party libraries.
     * @param {pc.Application} app The running {pc.Application}
     */
    var ScriptHandler = function (app) {
        this._app = app;
        this._scripts = { };
        this._cache = { };
    };

    ScriptHandler._types = [];
    ScriptHandler._push = function (Type) {
        if (pc.script.legacy && ScriptHandler._types.length > 0) {
            console.assert("Script Ordering Error. Contact support@playcanvas.com");
        } else {
            ScriptHandler._types.push(Type);
        }
    };

    Object.assign(ScriptHandler.prototype, {
        load: function (url, callback) {
            // Scripts don't support bundling since we concatenate them. Below is for consistency.
            if (typeof url === 'string') {
                url = {
                    load: url,
                    original: url
                };
            }

            var self = this;
            pc.script.app = this._app;

            this._loadScript(url.original, function (err, url, extra) {
                if (!err) {
                    if (pc.script.legacy) {
                        var Type = null;
                        // pop the type from the loading stack
                        if (ScriptHandler._types.length) {
                            Type = ScriptHandler._types.pop();
                        }

                        if (Type) {
                            // store indexed by URL
                            this._scripts[url] = Type;
                        } else {
                            Type = null;
                        }

                        // return the resource
                        callback(null, Type, extra);
                    } else {
                        var obj = { };

                        for (var i = 0; i < ScriptHandler._types.length; i++)
                            obj[ScriptHandler._types[i].name] = ScriptHandler._types[i];

                        ScriptHandler._types.length = 0;

                        callback(null, obj, extra);

                        // no cache for scripts
                        delete self._loader._cache[url + 'script'];
                    }
                } else {
                    callback(err);
                }
            }.bind(this));
        },

        open: function (url, data) {
            return data;
        },

        patch: function (asset, assets) { },

        _loadScript: function (url, callback) {
            var head = document.head;
            var element = document.createElement('script');
            this._cache[url] = element;

            // use async=false to force scripts to execute in order
            element.async = false;

            element.addEventListener('error', function (e) {
                callback(pc.string.format("Script: {0} failed to load", e.target.src));
            }, false);

            var done = false;
            element.onload = element.onreadystatechange = function () {
                if (!done && (!this.readyState || (this.readyState == "loaded" || this.readyState == "complete"))) {
                    done = true; // prevent double event firing
                    callback(null, url, element);
                }
            };
            // set the src attribute after the onload callback is set, to avoid an instant loading failing to fire the callback
            element.src = url;

            head.appendChild(element);
        }
    });

    return {
        ScriptHandler: ScriptHandler
    };
}());