Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load ordering of dynamically added script tags

I have a typescript application that dynamically adds script tags that point to JS files. Due to some restrictions I cant have these script tags statically defined in a html file, so I add them dynamically through typescript like this:

for (let src of jsFiles) {
  let sTag = document.createElement('script');
  sTag.type = 'text/javascript';
  sTag.src = src;
  sTag.defer = true;
  document.body.appendChild(script);
}

Now, I am noticing that, when I add script tags dynamically, their doesn't seem to be a guarantee in the order in which they are loaded. Unfortunately, the jsFiles array has scripts that are dependent on each other. So, the 2nd script in the array can only be loaded after the first one is fully loaded. The second script references a function that is defined in the first one. Is there a way I can specify the order in which scripts are ordered and executed when adding them dynamically (similar to how ordering is done when you statically define script tags in a html file)?

P.S. I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application. My second script file is very large and I am assuming that caused the degradation.

like image 498
RagHaven Avatar asked Aug 08 '16 22:08

RagHaven


People also ask

Are script tags loaded in order?

Script tags are executed in the order they appear It also means scripts which appear later on the page can depend on things scripts which appear earlier have done. Elements on the page won't render until all the script tags preceding them have loaded and executed.

Where do you put the script tag for best load speed?

Place the script Tag at the Bottom of the Page When you place the script tag at the bottom of the page after the main content, there will be some performance improvement. The content of the page will load and get parsed, as there is no blocking behavior for rendering HTML since you have placed the script in the end.

What is dynamic loading in JavaScript?

To load a JavaScript file dynamically: Create a script element. Set the src , async , and type attributes. Append the script element to the body. Check if the file loaded or not in the load event.

Which is the best placement of script tag?

The script tag should always be used before the body close or at the bottom in HTML file. The Page will load with HTML and CSS and later JavaScript will load.


1 Answers

I can mention some alternatives to overcome that requirement:

  1. Use a library to inject dependencies (AMD or CommonJS modules)
    Just use modules. ES2015: import/export, or CommonJS: require().
  2. Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously. The attribute async = true is set by default.
  3. If you are allowed to modify the scripts to inject, then add a line at the end of scripts with an object or array that keeps track of the scripts already loaded.
  4. You can fetch the scripts as text (XMLHttpRequest), then, build a string with the scripts in the required order and, finally, execute the text-script via eval()
  5. And the less recommended option but frequently used, set a setInterval to check if the script was already executed.

I recommend going for the first option. But for academic purposes, I'm going to illustrate the second option:

Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously.

I want to recommend a reading about script loaders: Deep dive into the murky waters of script loading, half hour worth spending!

The following example is a small module to manage scripts injection, and this is the basic idea behind it:

let _scriptsToLoad = [
  'path/to/script1.js',
  'path/to/script2.js',
  'path/to/script3.js'
];

function createScriptElement() {
  // gets the first script in the list
  let script = _scriptsToLoad.shift();
  // all scripts were loaded
  if (!script) return;
  let js = document.createElement('script');
  js.type = 'text/javascript';
  js.src = script;
  js.onload = onScriptLoaded;
  let s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(js, s);
}

function onScriptLoaded(event) {
  // loads the next script
  createScriptElement();
};

In this plunker you can test the injection of scripts asynchronously in a specific order:

  • https://plnkr.co/edit/b9O19f

The main idea was to create an API that allows you interact with the scripts to inject, by exposing the following methods:

  • addScript: receive an URL or a list of URLs for each script to be loaded.
  • load: Run the task to load scripts in the specified order.
  • reset: Clear the array of scripts, or cancels the load of scripts.
  • afterLoad: Callback executed after every script has been loaded.
  • onComplete: Callback executed after all scripts have been loaded.

I like Fluent Interface or method chaining technique, so I built the module that way:

  scriptsLoader
    .reset()
    .addScript("script1.js")
    .addScript(["script2.js", "script3.js"])
    .afterLoad((src) => console.warn("> loaded from jsuLoader:", src))
    .onComplete(() => console.info("* ALL SCRIPTS LOADED *"))
    .load();

In the code above, we load first the "script1.js" file, and execute the afterLoad() callback, next, do the same with "script2.js" and "script3.js" and after all scripts has been loaded, the onComplete() callback is executed.

like image 191
jherax Avatar answered Sep 28 '22 20:09

jherax