5

I need to dynamically load a JavaScript file and then access its content.

File test.js

test = function () {
    var pub = {}
    pub.defult_id = 1;
    return pub;
}()


In this case it works:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="/test.js"></script>    
</head>
<body>
    <script type="text/javascript">
        console.log(test.defult_id);
    </script>
</body>
</html>


But I need to load it dynamically, and that way it does not work:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <script type="text/javascript">
        function loadjs(file) {
            var script = document.createElement("script");
            script.type = "application/javascript";
            script.src = file;
            document.body.appendChild(script);
        }
        loadjs('test.js');
        console.log(test.defult_id);
    </script>
</body>
</html>


Error: Uncaught ReferenceError: test is not defined(…)

leonheess
  • 5,825
  • 6
  • 42
  • 67
Alexandre Neukirchen
  • 2,339
  • 7
  • 20
  • 32
  • Why the need for `location.href`? Just targeting `/test.js` will be the same as `location.href+'/test.js` apart from the issue of if a client visits and the current url ends with a `/` for example `www.domain.com/` then your javascript will target `www.domain.com//test.js` – NewToJS Nov 25 '16 at 15:47
  • 1
    Really do not need `location.href`. I've already changed. – Alexandre Neukirchen Nov 25 '16 at 15:54
  • Possible duplicate of [Dynamically load JS inside JS](https://stackoverflow.com/questions/14521108/dynamically-load-js-inside-js) – Nick Oct 16 '18 at 08:40
  • this question is related https://superuser.com/questions/1460015/how-can-i-make-a-bookmark-that-installs-jquery/1460016#1460016 – barlop Oct 07 '19 at 09:17

4 Answers4

15

You could do it like this:

function loadjs(file) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = file;
    script.onload = function(){
        alert("Script is ready!"); 
        console.log(test.defult_id);
    };
    document.body.appendChild(script);
 }

For more information read this article : https://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/

eztam
  • 2,351
  • 6
  • 25
  • 47
  • Here is a best contemporary article on this topic: [ Deep dive into the murky waters of script loading ] ( https://www.html5rocks.com/en/tutorials/speed/script-loading ) – asmmahmud Aug 03 '17 at 16:01
7

There is a great article which is worth reading for all the guys interesting in js script loading in www.html5rocks.com - Deep dive into the murky waters of script loading .

In that article after considering many possible solutions, the author concluded that adding js scripts to the end of body element is the best possible way to avoid blocking page rendering by js scripts thus speeding page loading time.

But, the author propose another good alternate solution for those people who are desperate to load and execute scripts asynchronously.

Considering you've four scripts named script1.js, script2.js, script3.js, script4.js then you can do it with applying async = false:

[
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

Now, Spec says: Download together, execute in order as soon as all download.

Firefox < 3.6, Opera says: I have no idea what this “async” thing is, but it just so happens I execute scripts added via JS in the order they’re added.

Safari 5.0 says: I understand “async”, but don’t understand setting it to “false” with JS. I’ll execute your scripts as soon as they land, in whatever order.

IE < 10 says: No idea about “async”, but there is a workaround using “onreadystatechange”.

Everything else says: I’m your friend, we’re going to do this by the book.

Now, the full code with IE < 10 workaround:

var scripts = [
  'script1.js',
  'script2.js',
  'script3.js',
  'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we’ll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

A few tricks and minification later, it’s 362 bytes

!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
  "//other-domain.com/1.js",
  "2.js"
])
asmmahmud
  • 4,066
  • 2
  • 32
  • 41
4

NOTE: there was one similar solution but it doesn't check if the script is already loaded and loads the script each time. This one checks src property and doesn't add script tag if already loaded. Loader function:

  const loadCDN = src =>
    new Promise((resolve, reject) => {
      if (document.querySelector(`head > script[src="${src}"]`) !== null) return resolve()
      const script = document.createElement("script")
      script.src = src
      script.async = true
      document.head.appendChild(script)
      script.onload = resolve
      script.onerror = reject
    })

Usage (async/await):

await loadCDN("https://.../script.js")

Usage (Promise):

loadCDN("https://.../script.js").then(res => {}).catch(err => {})
radulle
  • 1,095
  • 8
  • 16
  • So if I understand correctly, this checks if this script is already loaded on page and if it is, it will will not create another script and load the library second time? Kinda like a cache? – Limpuls Mar 09 '20 at 22:39
  • @Limpuls exacly. – radulle May 08 '20 at 18:31
2

Dinamically loading JS files is asynchronous, so to ensure your script is loaded before calling some function inside, use the onload event in script:

function loadjs(file) {
            var script = document.createElement("script");
            script.type = "application/javascript";
            script.onload=function(){
                //at this tine the script is loaded
                console.log("Script loaded!");
                console.log(test);
            }
            script.src = file;
            document.body.appendChild(script);
        }
F.Igor
  • 3,394
  • 1
  • 15
  • 21