Load External JavaScript Dynamically

Published January 29, 2024

Reading time: 2 minutes.


Once in a while you have to load external scripts and wait for them to load before you can do anything that uses them. I’ve worked with highlight.js on many projects to provide syntax highlighting for code. Usually I can integrate this with my build system.

Unfortunately, sometimes I have to integrate it with a third-party system, like an LMS platform, and I only have access to execute JavaScript in a specific place, like the <head> of a page. With Highlight.js, you may have several libraries to load based on the features you need.

Here’s how you solve it.

When loading an external JavaScript library through client code, you need to create a script element and set its src attributes, and then attach it to the DOM:

let tag = document.createElement('script');
tag.src = uri;
let scriptTag = document.getElementsByTagName('script')[0];
scriptTag.parentNode.insertBefore(tag, scriptTag);

In this example, the code looks for the first script tag on the page and inserts this new script before it.

Unfortunately, this may not be enough. You may need to wait for the script to load so you can load other scripts that depend on it.

The solution is to define a function that loads the scripts and returns a resolved Promise when the script loads:

var loadScript = function(uri){
  return new Promise((resolve, reject) => {
    let tag = document.createElement('script');
    tag.src = uri;
    tag.async = true;
    tag.onload = resolve; // Resolve on successful load
    tag.onerror = reject; // Reject on error
    let scriptTag = document.getElementsByTagName('script')[0];
    scriptTag.parentNode.insertBefore(tag, scriptTag);
  });
};

With this in place, you can now call this function to load the scripts and resolve the Promises:

const scriptPromise1 = loadScript('https://example.com/script1.js');
const scriptPromise2 = loadScript('https://example.com/script2.js');
const scriptPromise3 = loadScript('https://example.com/script3.js');

If you need them all to load and then run your specific code, use Promise.all():

Promise.all([ scriptPromise1, scriptPromise2, scriptPromise3  ])
  .then(() => {
    // execute your code that uses the scripts you loaded
    yourFunction();
})
  .catch(error => {
    console.error('Script loading error:', error);
  });

If you have a situation where you have a main script that you need to load, and subsequent scripts that must load once that one loads, load those when the first promise resolves:

// Load the main script
loadScript('https://example.com/highlight.js')
  .then(() => {

    // Load all dependent scripts
    let scripts = [
      'https://example.com/highlight-go.js',
      'https://example.com/highlight-markdown.js',
    ];

    return Promise.all(scripts.map(script => loadScript(script)));
  })
  .then(() => {
    // execute your code that uses the scripts you loaded
    yourFunction();
  })
  .catch(error => {
    console.error('Script loading error:', error);
  });

If you have the option to bundle your scripts as part of the build process, you should consider that instead. But for those cases where you must load external scripts through JavaScript code, this is an effective pattern.


Thanks for reading

I don't have comments enabled on this site, but I'd love to talk with you about this article on BlueSky, Mastodon, Twitter, or LinkedIn. Follow me there and say hi.


Liked this? I have a newsletter.