Load ordering of dynamically added script tags

可紊 提交于 2019-11-27 23:02:16

There are some ways to overcome that requirement. For now I can suggest the followings:

  1. Use a library to inject dependencies (AMD or CommonJS modules)
  2. Create the script tag programmatically and set the attribute async = false.
  3. 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. (See example below)
  4. If you don't like previous options and you are allowed to modify the scripts to inject, then add a line at the end of scripts with a object/array that keep track of the scripts loaded.
  5. As a last resource, you can fetch the scripts as text, build a string with the scripts in the required order (wrap each text-script inside an IIFE) and finally execute the text-script through the horrible and dangerous eval().
  6. And the worst option, use a setInterval to check whether the script was executed. I added this last option only because I have seen some solutions using this bad technique.

If you are working in a big project, I would recommend the first option. But if you have a small project, you can opt for the third option. Why?

Let's consider that in the second option, by setting the attribute async = false will cause the browser blocking rendering until the script has been executed (bad practice).

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

I wrote an example of a small module to manage scripts injection, and this is the basic idea behind:

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

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

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

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

  • addScript: receive the url or an Array of url's of the scripts.
  • load: Runs the task for load scripts in the specific 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 the Fluent Interface or method chaining way, then I built the module that way:

  jsuLoader
    .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 were loaded, the onComplete() callback is executed.

I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application.

If you want the files ordered ... you need to wait for onload on each file. No way around it.

Here is a utility function I wrote once:

/**
 * Utility : Resolves when a script has been loaded
 */
function include(url: string) {
  return new Promise<{ script: HTMLScriptElement }>((resolve, reject) => {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = function() {
      resolve({ script });
    };

    document.getElementsByTagName('head')[0].appendChild(script);
  });
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!