Search code examples
javascriptrequirejsglobal-variablesamddynamic-script-loading

How to circumvent RequireJS to load module with global?


I'm trying to load a JS file from a bookmarklet. The JS file has this JS that wraps the module:

(function (root, factory) {
    if (typeof module === 'object' && module.exports) {
        // Node/CommonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(factory);
    } else {
        // Browser globals
        root.moduleGlobal = factory();
    }
}(this, function factory() {
    // module script is in here

    return moduleGlobal;
}));

Because of this, if the webpage uses RequireJS, the script will not export a global when it loads. To get around this I temporarily set define to null, load the script, then reset define to its original value:

function loadScript(url, cb) {
    var s = document.createElement('script');
    s.src = url;
    s.defer = true;
    var avoidRequireJS = typeof define === 'function' && define.amd;
    if (avoidRequireJS) {
        var defineTmp = define;
        define = null;
    }
    s.onload = function() {
        if (avoidRequireJS) define = defineTmp;
        cb();
    };
    document.body.appendChild(s);
}

This works, but it seems to me like it could be problematic to change a global variable when other parts of the application could depend on it. Is there a better way to go about this?


Solution

  • You may fetch the script using XMLHttpRequest, jQuery.ajax or the new Fetch API.

    This will allow you to manipulate the script and reassign define before executing it. Two options:

    1. Have the module export a global by wrapping the script with:

      (function(define){ ... })(null);
      
    2. Handle the module exports yourself by wrapping the script with:

      (function(define, module){ ... })((function() {
          function define(factory) {
              var exports = factory();
          }
          define.amd = true;
          return define;
      })());
      

    You can then load it using a new <script> element or eval 😲.

    Note that when using XHR, you may have to address CORS issues.