1

I need to dynamically load a JS file which exports a variable to the window object (e.g. React or jQuery) and get the exported value without changing the page window object. How can I do it using JavaScript?

It should work like this:

(function () {
    var React = someMagic();
    assertNotEmpty(React);
    assertEmpty(window.React);
})();

What is my end goal: make a script for embedding to other websites. The script performs some actions with the page where it is installed, requires some dependencies, but the dependencies must not interfere with the page dependencies.


Using AMD or Require.js is not suitable because it changes the page scripts behaviour (they stop exporting variables to window) which can break the page.

Using a solution like this:

<script src="jquery.js"></script>
<script>
    (function () {
        var jQuery = jQuery.noConflict();
    })();
</script>

Is also not suitable because it requires changing HTML while I can use only JavaScript.

Joining the script with the dependencies and wrapping it all with (function(){ ... })() is not suitable too.

Finesse
  • 6,684
  • 6
  • 44
  • 60
  • I'm confused about what is actually suitable. AMD? no. Require.js? no. IIFE? no. ES6 Imports? idk – Jay Harris Mar 30 '18 at 05:06
  • @JayHarris IIFE is ok, but the dependencies must be loaded from other files – Finesse Mar 30 '18 at 05:07
  • https://stackoverflow.com/a/14521482/2284613 – Jay Harris Mar 30 '18 at 05:09
  • @JayHarris When a script is loaded this way, it exports variables to the `window` object. If a variable exists (e.g. loaded by the page), the script will overwrite it which can break the page. – Finesse Mar 30 '18 at 05:13

1 Answers1

0

Use Iframe to separate the namespaces. When a script is loaded inside an Iframe, it doesn't change the page namespace. A variable from an Iframe namespace can be exported to the page namespace when required.

// Imagine this is a content of the embedded script

const onMyJqueryLoad = jQuery => {
    console.log('Page jQuery:', window.jQuery.fn.jquery, window.jQuery.ui);
    console.log('My jQuery:', jQuery.fn.jquery, jQuery.ui);
};

const loadScript = (src, callback = () => {}, document = window.document) => {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.onload = script.onerror = () => {
        script.parentNode.removeChild(script);
        callback();
    };
    script.src = src;
    document.body.appendChild(script);
}

// Create an Iframe and do the dirty job inside it without affecting the page
let iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.top = '-1000px';
iframe.style.width = 0;
iframe.style.height = 0;
iframe.onload = () => {
    loadScript(
        'https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js',
        () => loadScript(
            'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.8.24/jquery-ui.min.js',
            () => {
                const jQuery = iframe.contentWindow.jQuery;
                iframe.parentNode.removeChild(iframe);
                iframe = null;
                onMyJqueryLoad(jQuery);
            },
            iframe.contentWindow.document
        ),
        iframe.contentWindow.document
    );
 
    iframe.onload = null;
};
document.body.appendChild(iframe);
<!-- The page -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>

Unfortunately the code doesn't work inside a Stack Overflow snippet because of the snippet sandboxing. You can view a demo on CodePen.

Finesse
  • 6,684
  • 6
  • 44
  • 60