Dynamically load a JavaScript file

194,142

Solution 1

You may create a script element dynamically, using Prototypes:

new Element("script", {src: "myBigCodeLibrary.js", type: "text/javascript"});

The problem here is that we do not know when the external script file is fully loaded.

We often want our dependant code on the very next line and like to write something like:

if (iNeedSomeMore) {
    Script.load("myBigCodeLibrary.js"); // includes code for myFancyMethod();
    myFancyMethod(); // cool, no need for callbacks!
}

There is a smart way to inject script dependencies without the need of callbacks. You simply have to pull the script via a synchronous AJAX request and eval the script on global level.

If you use Prototype the Script.load method looks like this:

var Script = {
    _loadedScripts: [],
    include: function(script) {
        // include script only once
        if (this._loadedScripts.include(script)) {
            return false;
        }
        // request file synchronous
        var code = new Ajax.Request(script, {
            asynchronous: false,
            method: "GET",
            evalJS: false,
            evalJSON: false
        }).transport.responseText;
        // eval code on global level
        if (Prototype.Browser.IE) {
            window.execScript(code);
        } else if (Prototype.Browser.WebKit) {
            $$("head").first().insert(Object.extend(
                new Element("script", {
                    type: "text/javascript"
                }), {
                    text: code
                }
            ));
        } else {
            window.eval(code);
        }
        // remember included script
        this._loadedScripts.push(script);
    }
};

Solution 2

There is no import / include / require in javascript, but there are two main ways to achieve what you want:

1 - You can load it with an AJAX call then use eval.

This is the most straightforward way but it's limited to your domain because of the Javascript safety settings, and using eval is opening the door to bugs and hacks.

2 - Add a script element with the script URL in the HTML.

Definitely the best way to go. You can load the script even from a foreign server, and it's clean as you use the browser parser to evaluate the code. You can put the script element in the head element of the web page, or at the bottom of the body.

Both of these solutions are discussed and illustrated here.

Now, there is a big issue you must know about. Doing that implies that you remotely load the code. Modern web browsers will load the file and keep executing your current script because they load everything asynchronously to improve performances.

It means that if you use these tricks directly, you won't be able to use your newly loaded code the next line after you asked it to be loaded, because it will be still loading.

E.G : my_lovely_script.js contains MySuperObject

var js = document.createElement("script");

js.type = "text/javascript";
js.src = jsFilePath;

document.body.appendChild(js);

var s = new MySuperObject();

Error : MySuperObject is undefined

Then you reload the page hitting F5. And it works! Confusing...

So what to do about it ?

Well, you can use the hack the author suggests in the link I gave you. In summary, for people in a hurry, he uses en event to run a callback function when the script is loaded. So you can put all the code using the remote library in the callback function. E.G :

function loadScript(url, callback)
{
    // adding the script element to the head as suggested before
   var head = document.getElementsByTagName('head')[0];
   var script = document.createElement('script');
   script.type = 'text/javascript';
   script.src = url;

   // then bind the event to the callback function 
   // there are several events for cross browser compatibility
   script.onreadystatechange = callback;
   script.onload = callback;

   // fire the loading
   head.appendChild(script);
}

Then you write the code you want to use AFTER the script is loaded in a lambda function :

var myPrettyCode = function() {
    // here, do what ever you want
};

Then you run all that :

loadScript("my_lovely_script.js", myPrettyCode);

Ok, I got it. But it's a pain to write all this stuff.

Well, in that case, you can use as always the fantastic free jQuery framework, which let you do the very same thing in one line :

$.getScript("my_lovely_script.js", function() {
    alert("Script loaded and executed.");
    // here you can use anything you defined in the loaded script
});

Solution 3

I used a much less complicated version recently with jQuery:

<script src="scripts/jquery.js"></script>
<script>
  var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
  var $head = $("head");
  for (var i = 0; i < js.length; i++) {
    $head.append("<script src=\"" + js[i] + "\"></scr" + "ipt>");
  }
</script>

It worked great in every browser I tested it in: IE6/7, Firefox, Safari, Opera.

Update: jQuery-less version:

<script>
  var js = ["scripts/jquery.dimensions.js", "scripts/shadedborder.js", "scripts/jqmodal.js", "scripts/main.js"];
  for (var i = 0, l = js.length; i < l; i++) {
    document.getElementsByTagName("head")[0].innerHTML += ("<script src=\"" + js[i] + "\"></scr" + "ipt>");
  }
</script>

Solution 4

I did basically the same thing that you did Adam, but with a slight modification to make sure I was appending to the head element to get the job done. I simply created an include function (code below) to handle both script and CSS files.

This function also checks to make sure that the script or CSS file hasn't already been loaded dynamically. It does not check for hand coded values and there may have been a better way to do that, but it served the purpose.

function include( url, type ){
    // First make sure it hasn't been loaded by something else.
    if( Array.contains( includedFile, url ) )
        return;
     
    // Determine the MIME type.
    var jsExpr = new RegExp( "js$", "i" );
    var cssExpr = new RegExp( "css$", "i" );
    if( type == null )
        if( jsExpr.test( url ) )
            type = 'text/javascript';
        else if( cssExpr.test( url ) )
            type = 'text/css';
            
    // Create the appropriate element.
    var element = null;
    switch( type ){
        case 'text/javascript' :
            element = document.createElement( 'script' );
            element.type = type;
            element.src = url;
            break;
        case 'text/css' :
            element = document.createElement( 'link' );
            element.rel = 'stylesheet';
            element.type = type;
            element.href = url;
            break;
    }
    
    // Insert it to the <head> and the array to ensure it is not
    // loaded again.
    document.getElementsByTagName("head")[0].appendChild( element );
    Array.add( includedFile, url );
}

Solution 5

another awesome answer

$.getScript("my_lovely_script.js", function(){


   alert("Script loaded and executed.");
  // here you can use anything you defined in the loaded script

 });

https://stackoverflow.com/a/950146/671046

Share:
194,142

Related videos on Youtube

Rob
Author by

Rob

I like computers, sports, ultimate frisbee, and good times.

Updated on May 02, 2022

Comments

  • Rob
    Rob about 2 years

    How can you reliably and dynamically load a JavaScript file? This will can be used to implement a module or component that when 'initialized' the component will dynamically load all needed JavaScript library scripts on demand.

    The client that uses the component isn't required to load all the library script files (and manually insert <script> tags into their web page) that implement this component - just the 'main' component script file.

    How do mainstream JavaScript libraries accomplish this (Prototype, jQuery, etc)? Do these tools merge multiple JavaScript files into a single redistributable 'build' version of a script file? Or do they do any dynamic loading of ancillary 'library' scripts?

    An addition to this question: is there a way to handle the event after a dynamically included JavaScript file is loaded? Prototype has document.observe for document-wide events. Example:

    document.observe("dom:loaded", function() {
      // initially hide all containers for tab content
      $$('div.tabcontent').invoke('hide');
    });
    

    What are the available events for a script element?

  • Admin
    Admin about 14 years
    That's great... unless you're trying to load jquery.
  • palehorse
    palehorse about 14 years
    Without more context than that Pickle, I'm afraid I don't have any suggestions. The code above works as-is, it was pulled directly from a functioning project.
  • Rob
    Rob about 13 years
    Looks like jQuery is going to roll in the Require plugin into jQuery Core for a future release: plugins.jquery.com/project/require
  • Muhd
    Muhd about 12 years
    This is probably ideal except for YUI being obscenely large for just this functionality.
  • Muhd
    Muhd about 12 years
    An even better way to use jQuery is to use $.getScript. See my answer.
  • 1nfiniti
    1nfiniti about 12 years
    Modernizr (yepnope.js) or lab.js are the appropriate solutions for this. using a heavy script library (which must load first) is not the most suitable answer for mobile or many other situations.
  • JonnyRaa
    JonnyRaa about 10 years
    nice answer but why write a for loop on 1 line?!
  • travis
    travis about 10 years
    @JonnyLeeds good point, I was being too compact, I'll update.
  • user2284570
    user2284570 over 9 years
    What can I do to get it working for cross-domain? (loading script from http://web.archive.org/web/20140905044059/http://www.howtocr‌​eate.co.uk/operaStu‌‌​​ff/userjs/aagmfunct‌​ions.js)
  • user2284570
    user2284570 over 9 years
    What can I do to get it working for cross-domain? (loading script from http://web.archive.org/web/20140905044059/http://www.howtocr‌​eate.co.uk/operaStu‌‌​​ff/userjs/aagmfunct‌​ions.js)
  • user2284570
    user2284570 over 9 years
    What can I do to get it working for cross-domain? (loading script from http://web.archive.org/web/20140905044059/http://www.howtocr‌​eate.co.uk/operaStu‌‌​​ff/userjs/aagmfunct‌​ions.js)
  • user2284570
    user2284570 over 9 years
    What can I do to get it working for cross-domain? (loading script from http://web.archive.org/web/20140905044059/http://www.howtocr‌​eate.co.uk/operaStu‌‌​​ff/userjs/aagmfunct‌​ions.js)
  • user2284570
    user2284570 over 9 years
    What can I do to get it working for cross-domain? (loading script from http://web.archive.org/web/20140905044059/http://www.howtocr‌​eate.co.uk/operaStu‌‌​​ff/userjs/aagmfunct‌​ions.js)
  • user280109
    user280109 over 9 years
    @palehorse, Mike and Muhd, are right, it might work in your project, but thats because the "includedFile" and "Array" variables must be defined elsewhere in your project, this code on its own won't run, it might be better to edit it so it can work outside the context of your project, or at least add a comment explaining what those undefined variables are (type etc)
  • Ciasto piekarz
    Ciasto piekarz over 7 years
  • user2284570
    user2284570 over 7 years
    @Ciastopiekarz : I don’t controlweb.archive.org.
  • travis
    travis about 7 years
    @MaulikGangani older browsers and html validators would interpret that as the token to end the script.
  • asmmahmud
    asmmahmud almost 7 years
    you can take a look at the link: answer
  • asmmahmud
    asmmahmud almost 7 years
    you can take a look at the link: answer
  • David Schumann
    David Schumann over 6 years
    Then you have to scrape the data you want to access using some other program and provide it to yourself
  • Nour Lababidi
    Nour Lababidi over 5 years
    This works. I hope sharing my experience here saves your time because I spent many hours on this. I'm using Angular 6 and applied template (html,css,jquery) The problem is the template has a js file which loads after the html elements are loaded to attach listeners events. That js file was hard to executre after angular app is loaded. Adding it to angular app ( angular.json) scripts tag will bundle them but not execute that js file after loading. It is too much code to rewrite in typescript so this was great help. Next comment I'll put the example becuse of comment length
  • Nour Lababidi
    Nour Lababidi over 5 years
    I simply used this code as follow: ngAfterViewInit() { debugger; $.getScript("/assets/js/jquery.app.js", function() { alert("Script loaded and executed."); // here you can use anything you defined in the loaded script }); }
  • Nour Lababidi
    Nour Lababidi over 5 years
    For the '$' in angular I followed this : stackoverflow.com/questions/32050645/…
  • Mr. Roshan
    Mr. Roshan over 4 years
    I am using above approach it's working fine in chrome & firefox but facing problems in IE browser
  • Michael Hoffmann
    Michael Hoffmann over 3 years
    Is there a reason to use the Function constructor instead of eval? From the MDN documentation Function just creates a function and takes parameter names then the function body as arguments. Whereas eval just takes a string of code, which is what you'd have in a .js file.
  • ggorlen
    ggorlen over 3 years
    Portions of this post seem plagiarized from stackoverflow.com/a/950146/6243352
  • Ludmil Tinkov
    Ludmil Tinkov almost 3 years
    @MichaelHoffmann, as far as I remember, I read somewhere that Function was a bit safer than eval, but I'll have to double-check that.
  • handaru
    handaru almost 3 years
    It works. Finally I solved my CSP nonce problem with it. Thank you.
  • NVRM
    NVRM almost 3 years
    This is useful for local, but may create some XSS weakness otherwise. On this case, both eval and Function are not safe.
  • Marcel Waldvogel
    Marcel Waldvogel over 2 years
    This works in every browser having received updates in the past two years and is much cleaner than the <script> addition hacks that were required before. To ensure that new users will learn the modern way directly, this should be the accepted answer.
  • Ludo
    Ludo about 2 years
    If loading a file with a relative path, use ./ e.g. import('./you_path/your_file.js')
  • vatavale
    vatavale about 2 years
    This function can fire resolve() before script is actually loaded.
  • radulle
    radulle about 2 years
    Actually resolve is called only once script is loaded. Ofcourse, if your environment allows using dynamic module imports is optimal solution.
  • vatavale
    vatavale about 2 years
    Yes, some of the functionality is there. But in two cases, this function can give false results - if you call it twice quickly and if the document had a tag with the script from the beginning and the function was called before the script was loaded. If these scenarios are excluded, then it's fine.