Related
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?
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);
}
};
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
});
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>
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 );
}
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
Dynamic module import landed in Firefox 67+.
(async () => {
await import('./synth/BubbleSynth.js')
})()
With error handling:
(async () => {
await import('./synth/BubbleSynth.js').catch((error) => console.log('Loading failed' + error))
})()
It also works for any kind of non-modules libraries, on this case the lib is available on the window.self object, the old way, but only on demand, which is nice.
Example using suncalc.js, the server must have CORS enabled to works this way!
(async () => {
await import('https://cdnjs.cloudflare.com/ajax/libs/suncalc/1.8.0/suncalc.min.js')
.then( () => {
let times = SunCalc.getTimes(new Date(), 51.5,-0.1);
console.log("Golden Hour today in London: " + times.goldenHour.getHours() + ':' + times.goldenHour.getMinutes() + ". Take your pics!")
})
})()
https://caniuse.com/#feat=es6-module-dynamic-import
Here is some example code I've found... does anyone have a better way?
function include(url)
{
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
var nodes = document.getElementsByTagName("*");
var node = nodes[nodes.length -1].parentNode;
node.appendChild(s);
}
If you have jQuery loaded already, you should use $.getScript.
This has an advantage over the other answers here in that you have a built in callback function (to guarantee the script is loaded before the dependant code runs) and you can control caching.
With Promises you can simplify it like this.
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 => {})
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.
If you want a SYNC script loading, you need to add script text directly to HTML HEAD element. Adding it as will trigger an ASYNC load. To load script text from external file synchronously, use XHR. Below a quick sample (it is using parts of other answers in this and other posts):
/*sample requires an additional method for array prototype:*/
if (Array.prototype.contains === undefined) {
Array.prototype.contains = function (obj) {
var i = this.length;
while (i--) { if (this[i] === obj) return true; }
return false;
};
};
/*define object that will wrap our logic*/
var ScriptLoader = {
LoadedFiles: [],
LoadFile: function (url) {
var self = this;
if (this.LoadedFiles.contains(url)) return;
var xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
self.LoadedFiles.push(url);
self.AddScript(xhr.responseText);
} else {
if (console) console.error(xhr.statusText);
}
}
};
xhr.open("GET", url, false);/*last parameter defines if call is async or not*/
xhr.send(null);
},
AddScript: function (code) {
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
};
/*Load script file. ScriptLoader will check if you try to load a file that has already been loaded (this check might be better, but I'm lazy).*/
ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
/*this will be executed right after upper lines. It requires jquery to execute. It requires a HTML input with id "tb1"*/
$(function () { alert($('#tb1').val()); });
does anyone have a better way?
I think just adding the script to the body would be easier then adding it to the last node on the page. How about this:
function include(url) {
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
document.body.appendChild(s);
}
i've used yet another solution i found on the net ... this one is under creativecommons and it checks if the source was included prior to calling the function ...
you can find the file here: include.js
/** include - including .js files from JS - bfults#gmail.com - 2005-02-09
** Code licensed under Creative Commons Attribution-ShareAlike License
** http://creativecommons.org/licenses/by-sa/2.0/
**/
var hIncludes = null;
function include(sURI)
{
if (document.getElementsByTagName)
{
if (!hIncludes)
{
hIncludes = {};
var cScripts = document.getElementsByTagName("script");
for (var i=0,len=cScripts.length; i < len; i++)
if (cScripts[i].src) hIncludes[cScripts[i].src] = true;
}
if (!hIncludes[sURI])
{
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.src = sURI;
hIncludes[sURI]=true;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
}
}
Just found out about a great feature in YUI 3 (at the time of writing available in preview release). You can easily insert dependencies to YUI libraries and to "external" modules (what you are looking for) without too much code: YUI Loader.
It also answers your second question regarding the function being called as soon as the external module is loaded.
Example:
YUI({
modules: {
'simple': {
fullpath: "http://example.com/public/js/simple.js"
},
'complicated': {
fullpath: "http://example.com/public/js/complicated.js"
requires: ['simple'] // <-- dependency to 'simple' module
}
},
timeout: 10000
}).use('complicated', function(Y, result) {
// called as soon as 'complicated' is loaded
if (!result.success) {
// loading failed, or timeout
handleError(result.msg);
} else {
// call a function that needs 'complicated'
doSomethingComplicated(...);
}
});
Worked perfectly for me and has the advantage of managing dependencies. Refer to the YUI documentation for an example with YUI 2 calendar.
I know my answer is bit late for this question, but, here is a great article in www.html5rocks.com - Deep dive into the murky waters of script loading .
In that article it is concluded that in regards of browser support, the best way to dynamically load JavaScript file without blocking content rendering is the following way:
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"
])
There's a new proposed ECMA standard called dynamic import, recently incorporated into Chrome and Safari.
const moduleSpecifier = './dir/someModule.js';
import(moduleSpecifier)
.then(someModule => someModule.foo()); // executes foo method in someModule
The technique we use at work is to request the javascript file using an AJAX request and then eval() the return. If you're using the prototype library, they support this functionality in their Ajax.Request call.
jquery resolved this for me with its .append() function
- used this to load the complete jquery ui package
/*
* FILENAME : project.library.js
* USAGE : loads any javascript library
*/
var dirPath = "../js/";
var library = ["functions.js","swfobject.js","jquery.jeditable.mini.js","jquery-ui-1.8.8.custom.min.js","ui/jquery.ui.core.min.js","ui/jquery.ui.widget.min.js","ui/jquery.ui.position.min.js","ui/jquery.ui.button.min.js","ui/jquery.ui.mouse.min.js","ui/jquery.ui.dialog.min.js","ui/jquery.effects.core.min.js","ui/jquery.effects.blind.min.js","ui/jquery.effects.fade.min.js","ui/jquery.effects.slide.min.js","ui/jquery.effects.transfer.min.js"];
for(var script in library){
$('head').append('<script type="text/javascript" src="' + dirPath + library[script] + '"></script>');
}
To Use - in the head of your html/php/etc after you import jquery.js you would just include this one file like so to load in the entirety of your library appending it to the head...
<script type="text/javascript" src="project.library.js"></script>
Keep it nice, short, simple, and maintainable! :]
// 3rd party plugins / script (don't forget the full path is necessary)
var FULL_PATH = '', s =
[
FULL_PATH + 'plugins/script.js' // Script example
FULL_PATH + 'plugins/jquery.1.2.js', // jQuery Library
FULL_PATH + 'plugins/crypto-js/hmac-sha1.js', // CryptoJS
FULL_PATH + 'plugins/crypto-js/enc-base64-min.js' // CryptoJS
];
function load(url)
{
var ajax = new XMLHttpRequest();
ajax.open('GET', url, false);
ajax.onreadystatechange = function ()
{
var script = ajax.response || ajax.responseText;
if (ajax.readyState === 4)
{
switch(ajax.status)
{
case 200:
eval.apply( window, [script] );
console.log("library loaded: ", url);
break;
default:
console.log("ERROR: library not loaded: ", url);
}
}
};
ajax.send(null);
}
// initialize a single load
load('plugins/script.js');
// initialize a full load of scripts
if (s.length > 0)
{
for (i = 0; i < s.length; i++)
{
load(s[i]);
}
}
This code is simply a short functional example that could require additional feature functionality for full support on any (or given) platform.
Something like this...
<script>
$(document).ready(function() {
$('body').append('<script src="https://maps.googleapis.com/maps/api/js?key=KEY&libraries=places&callback=getCurrentPickupLocation" async defer><\/script>');
});
</script>
This works:
await new Promise((resolve, reject) => {
let js = document.createElement("script");
js.src = "mylibrary.js";
js.onload = resolve;
js.onerror = reject;
document.body.appendChild(js)
});
Obviously if the script you want to import is a module, you can use the import(...) function.
There are scripts that are designed specifically for this purpose.
yepnope.js is built into Modernizr, and lab.js is a more optimized (but less user friendly version.
I wouldn't reccomend doing this through a big library like jquery or prototype - because one of the major benefits of a script loader is the ability to load scripts early - you shouldn't have to wait until jquery & all your dom elements load before running a check to see if you want to dynamically load a script.
I wrote a simple module that automatizes the job of importing/including module scripts in JavaScript. Give it a try and please spare some feedback! :) For detailed explanation of the code refer to this blog post: http://stamat.wordpress.com/2013/04/12/javascript-require-import-include-modules/
var _rmod = _rmod || {}; //require module namespace
_rmod.on_ready_fn_stack = [];
_rmod.libpath = '';
_rmod.imported = {};
_rmod.loading = {
scripts: {},
length: 0
};
_rmod.findScriptPath = function(script_name) {
var script_elems = document.getElementsByTagName('script');
for (var i = 0; i < script_elems.length; i++) {
if (script_elems[i].src.endsWith(script_name)) {
var href = window.location.href;
href = href.substring(0, href.lastIndexOf('/'));
var url = script_elems[i].src.substring(0, script_elems[i].length - script_name.length);
return url.substring(href.length+1, url.length);
}
}
return '';
};
_rmod.libpath = _rmod.findScriptPath('script.js'); //Path of your main script used to mark the root directory of your library, any library
_rmod.injectScript = function(script_name, uri, callback, prepare) {
if(!prepare)
prepare(script_name, uri);
var script_elem = document.createElement('script');
script_elem.type = 'text/javascript';
script_elem.title = script_name;
script_elem.src = uri;
script_elem.async = true;
script_elem.defer = false;
if(!callback)
script_elem.onload = function() {
callback(script_name, uri);
};
document.getElementsByTagName('head')[0].appendChild(script_elem);
};
_rmod.requirePrepare = function(script_name, uri) {
_rmod.loading.scripts[script_name] = uri;
_rmod.loading.length++;
};
_rmod.requireCallback = function(script_name, uri) {
_rmod.loading.length--;
delete _rmod.loading.scripts[script_name];
_rmod.imported[script_name] = uri;
if(_rmod.loading.length == 0)
_rmod.onReady();
};
_rmod.onReady = function() {
if (!_rmod.LOADED) {
for (var i = 0; i < _rmod.on_ready_fn_stack.length; i++){
_rmod.on_ready_fn_stack[i]();
});
_rmod.LOADED = true;
}
};
//you can rename based on your liking. I chose require, but it can be called include or anything else that is easy for you to remember or write, except import because it is reserved for future use.
var require = function(script_name) {
var np = script_name.split('.');
if (np[np.length-1] === '*') {
np.pop();
np.push('_all');
}
script_name = np.join('.');
var uri = _rmod.libpath + np.join('/')+'.js';
if (!_rmod.loading.scripts.hasOwnProperty(script_name)
&& !_rmod.imported.hasOwnProperty(script_name)) {
_rmod.injectScript(script_name, uri,
_rmod.requireCallback,
_rmod.requirePrepare);
}
};
var ready = function(fn) {
_rmod.on_ready_fn_stack.push(fn);
};
// ----- USAGE -----
require('ivar.util.array');
require('ivar.util.string');
require('ivar.net.*');
ready(function(){
//do something when required scripts are loaded
});
I am lost in all these samples but today I needed to load an external .js from my main .js and I did this:
document.write("<script src='https://www.google.com/recaptcha/api.js'></script>");
Here is a simple one with callback and IE support:
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function () {
//jQuery loaded
console.log('jquery loaded');
});
Here a simple example for a function to load JS files. Relevant points:
you don't need jQuery, so you may use this initially to load also the jQuery.js file
it is async with callback
it ensures it loads only once, as it keeps an enclosure with the record of loaded urls, thus avoiding usage of network
contrary to jQuery $.ajax or $.getScript you can use nonces, solving thus issues with CSP unsafe-inline. Just use the property script.nonce
var getScriptOnce = function() {
var scriptArray = []; //array of urls (closure)
//function to defer loading of script
return function (url, callback){
//the array doesn't have such url
if (scriptArray.indexOf(url) === -1){
var script=document.createElement('script');
script.src=url;
var head=document.getElementsByTagName('head')[0],
done=false;
script.onload=script.onreadystatechange = function(){
if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
done=true;
if (typeof callback === 'function') {
callback();
}
script.onload = script.onreadystatechange = null;
head.removeChild(script);
scriptArray.push(url);
}
};
head.appendChild(script);
}
};
}();
Now you use it simply by
getScriptOnce("url_of_your_JS_file.js");
For those of you, who love one-liners:
import('./myscript.js');
Chances are you might get an error, like:
Access to script at 'http://..../myscript.js' from origin
'http://127.0.0.1' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
In which case, you can fallback to:
fetch('myscript.js').then(r => r.text()).then(t => new Function(t)());
In as much as I love how handy the JQuery approach is, the JavaScript approach isn't that complicated but just require little tweaking to what you already use...
Here is how I load JS dynamically(Only when needed), and wait for them to load before executing the script that depends on them.
JavaScript Approach
//Create a script element that will load
let dynamicScript = document.createElement('script');
//Set source to the script we need to load
dynamicScript.src = 'linkToNeededJsFile.js';
//Set onload to callback function that depends on this script or do inline as shown below
dynamicScript.onload = () => {
//Code that depends on the loaded script should be here
};
//append the created script element to body element
document.body.append(dynamicScript);
There are other ways approach one could accomplish this with JS but, I prefer this as it's require the basic JS knowledge every dev can relate.
Not part of the answer but here is the JQuery version I prefer with projects that already include JQuery:
$.getScript('linkToNeededJsFile.js', () => {
//Code that depends on the loaded script should be here
});
More on the JQuery option here
This function uses memorization. And could be called many times with no conflicts of loading and running the same script twice. Also it's not resolving sooner than the script is actually loaded (like in #radulle answer).
const loadScript = function () {
let cache = {};
return function (src) {
return cache[src] || (cache[src] = new Promise((resolve, reject) => {
let s = document.createElement('script');
s.defer = true;
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.append(s);
}));
}
}();
Please notice the parentheses () after the function expression.
Parallel loading of scripts:
Promise.all([
loadScript('/script1.js'),
loadScript('/script2.js'),
// ...
]).then(() => {
// do something
})
You can use the same method for dynamic loading stylesheets.
all the major javascript libraries like jscript, prototype, YUI have support for loading script files. For example, in YUI, after loading the core you can do the following to load the calendar control
var loader = new YAHOO.util.YUILoader({
require: ['calendar'], // what components?
base: '../../build/',//where do they live?
//filter: "DEBUG", //use debug versions (or apply some
//some other filter?
//loadOptional: true, //load all optional dependencies?
//onSuccess is the function that YUI Loader
//should call when all components are successfully loaded.
onSuccess: function() {
//Once the YUI Calendar Control and dependencies are on
//the page, we'll verify that our target container is
//available in the DOM and then instantiate a default
//calendar into it:
YAHOO.util.Event.onAvailable("calendar_container", function() {
var myCal = new YAHOO.widget.Calendar("mycal_id", "calendar_container");
myCal.render();
})
},
// should a failure occur, the onFailure function will be executed
onFailure: function(o) {
alert("error: " + YAHOO.lang.dump(o));
}
});
// Calculate the dependency and insert the required scripts and css resources
// into the document
loader.insert();
I have tweaked some of the above post with working example.
Here we can give css and js in same array also.
$(document).ready(function(){
if (Array.prototype.contains === undefined) {
Array.prototype.contains = function (obj) {
var i = this.length;
while (i--) { if (this[i] === obj) return true; }
return false;
};
};
/* define object that will wrap our logic */
var jsScriptCssLoader = {
jsExpr : new RegExp( "js$", "i" ),
cssExpr : new RegExp( "css$", "i" ),
loadedFiles: [],
loadFile: function (cssJsFileArray) {
var self = this;
// remove duplicates with in array
cssJsFileArray.filter((item,index)=>cssJsFileArray.indexOf(item)==index)
var loadedFileArray = this.loadedFiles;
$.each(cssJsFileArray, function( index, url ) {
// if multiple arrays are loaded the check the uniqueness
if (loadedFileArray.contains(url)) return;
if( self.jsExpr.test( url ) ){
$.get(url, function(data) {
self.addScript(data);
});
}else if( self.cssExpr.test( url ) ){
$.get(url, function(data) {
self.addCss(data);
});
}
self.loadedFiles.push(url);
});
// don't load twice accross different arrays
},
addScript: function (code) {
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
},
addCss: function (code) {
var oNew = document.createElement("style");
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
};
//jsScriptCssLoader.loadFile(["css/1.css","css/2.css","css/3.css"]);
jsScriptCssLoader.loadFile(["js/common/1.js","js/2.js","js/common/file/fileReader.js"]);
});
I've attempting to create a custom embeddable widget that my users can include on their website.
We provide the code and then add it to their site and then they get access to a bunch of custom widgets that we've produced from our SaaS.
This works great if the site they are trying to embed the widget on DOES NOT already use requirejs, however, when it already uses requirejs, we get the dreaded...
Error: defineAlreadyDefined(…) error in console.
What we're doing is as follows:
Code We Provide Them
They would have instructions to add something like this to their site:
var foobar_load = function(){
require([
"foobar/rental!53891",
"widget/PriceAvailabilityCalculator" ,
"foobar/widget!/property/53891",
], function( Bridge, PriceAvailabilityCalculator, Availability){
new PriceAvailabilityCalculator({}).place("widget-container-1");
Availability.place("widget-container-2");
});
};
(function() {
var d = document,
h = d.getElementsByTagName('head')[0],
s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://www.example.com/v1/sdk.js';
h.appendChild(s);
} )();
</script>
This would then load our "sdk" code which is just a wrapper of the general bootstrapping process that you would need to do to include dojo on your site.
Loader Code
So it looks like this:
dojoConfig = {
async: true,
parseOnLoad: false,
locale: 'en-gb',
selectorEngine: 'css3',
paths: {
't' : '//dev.foobar.com/utils/i18n',
'my' : '//dev.foobar.com/js/custom/my',
'foobar' : '//dev.foobar.com/js/sdk',
'widget' : '//dev.foobar.com/js/custom/my/frontend'
},
packages: [
{ name: 'currency', location: '//dev.foobar.com/currency' },
{ name: 'x-application', location: '//dev.foobar.com/x-application' }
]
};
(function() {
var d = document,
h = d.getElementsByTagName('head')[0],
s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
// If foobar_load is missing.. what should happen?
s.onload= function(){
require(["dojo/ready", "dojo/domReady!", 'x-application'],
function(ready, x,r){
ready(0, foobar_load );
});
};
s.src = '//dev.foobar.com/js/dojo/1_9_7/dojo/dojo.js?load';
h.appendChild(s);
} )();
Now, I'm not sure what would be the things we need to do in order to make sure that we can use this custom code on a website which also uses RequireJS.
After playing around and getting some advice on #dojo IRC, this is the solution that I'm working with.
It's not a very clean solution but it works. If you have a cleaner solution, please do post your idea.. I'm leaving this here to help other people with the same issue.
The idea is to switch out the require and define functions and then switch them back AFTER dojo.js has finished loading. (A cleaner solution would be to not load dojo.js at all).
It looks like this:
First
if(typeof define !== "undefined" && typeof require !== "undefined"){
window.__define = window.define;
window.__require = window.require;
window.define = undefined;
window.require = undefined;
}
Then after dojo.js loaded
if( typeof window.__define !== "undefined" ) {
window.define = window.__define;
window.require = window.__require;
window.__define = undefined;
window.__require = undefined;
}
Question 1: How does this code load css files asynchronously ?
I understand that script files are loaded asynchrounously because of script.async = true; but can't seem to find a similar line for css files.
Question 2: if this script uses script.async = true; does it still work on the newer browsers since async src"" exist or does this script cover it?
Question 3: does the script.async = true; only load javascript, jquery and ajax files asynchronously?
( function ( window, doc ) {
asyncLoader = function( urls, options ) {
urls.foreach( function( i, file ) {
loadFile( file, getExtension( file ), options );
});
// checking for a callback function
if( typeof options.callback == 'function') {
// calling the callback
var readyStateCheckInterval = setInterval( function() {
if( doc.readyState === 'complete' ) {
clearInterval( readyStateCheckInterval );
options.callback();
}
}, 10 );
}
}
var getExtension = function( file ) {
var extension = file.split('.');
return extension[extension.length - 1];
},
loadFile = function( file, ext, options ) {
switch( ext ) {
case 'js' :
loadJs( file, options );
break;
case 'css' :
loadCss( file );
break;
default :
break;
}
},
loadJs = function( url, options ) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = url;
document.getElementsByTagName('head')[0].appendChild( script );
},
loadCss = function( url ) {
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
document.getElementsByTagName('head')[0].appendChild( link );
}
// simple foreach implementation
Array.prototype.foreach = function( callback ) {
for( var i=0; i<this .length; i++ ) {
callback( i, this[ i ] );
}
}
})( this, document );
The CSS file is loaded asynchronously because it is evaluated immediately when it is appended. Append does this asynchronously while the rest of the DOM executes. I've seen some people use this same trick with a tag without using async = true and it seems to work as well without using async = true.
This still works on new browsers, even if you add the async attribute to your or tags. However, the javascript way is more of a workaround solution compared to using the new HTML5 async attribute and so solely using the async attribute inline is preferred if your not worried about supporting older browsers for async loading. The async attribute works much better and is built to work better and more intuitively with the DOM.
According to your switch statement, the script.async = true is only being called in the loadJs() function, which is only being called if the extension is '.js'. So yes, javascript, jQuery and ajax files will be loaded using script.async = true, because they have a '.js' extension, but so will any other '.js' file.
Here's a decent resource for more reading on the javascript async trick: async-javascript
I have done several searches for this and found many examples for lazy-loading, but I can't find just the right example for what I'm trying to accomplish. Here's the down and dirty...
I have an object, call it Library:
(function() {
function Library() {
// some code in here
this.path = "/library";
}
function library() {
var l = new Library;
l.lang('en');
return l;
}
Library.prototype.lang = function(lang) {
lang = lang || 'en';
var s = document.createElement( "script" );
s.setAttribute("type", "text/javascript");
s.setAttribute("src", this.path + "/lang/" + lang + ".js");
document.getElementsByTagName( "head" )[0].appendChild( s );
}
})();
So, what I'm trying to do is lazy-load a "language" file to augment the Library object. The language file would like something like:
(function( Library ) {
Library.prototype._lang = {
header: "Welcome",
greet: "Hello",
exit: "Goodbye"
};
} ).bind( this );
The problem is (obviously), because of the way it's loaded, the new Library object is returned before it has a chance to be augmented. I have tried adding an event listener to the document appending the new script tag, but that won't let me "return" the library object. I'm having trouble finding the right way to defer the return of the library object.
I know about require.js. It's a great lib, but I don't want to use it for this project.
Thanks for helping!
You can load the file using a synchronous XMLHTTPRequest and eval:
(function() {
// ...
Library.prototype.lang = function(lang) {
lang = lang || 'en';
load(this.path + "/lang/" + lang + ".js");
};
function load(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send();
if (xhr.status !== 200) {
console.log("Failed to load " + url + ": " + xhr.statusText);
} else {
eval(xhr.responseText)(Library);
}
}
})();
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?
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);
}
};
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
});
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>
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 );
}
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
Dynamic module import landed in Firefox 67+.
(async () => {
await import('./synth/BubbleSynth.js')
})()
With error handling:
(async () => {
await import('./synth/BubbleSynth.js').catch((error) => console.log('Loading failed' + error))
})()
It also works for any kind of non-modules libraries, on this case the lib is available on the window.self object, the old way, but only on demand, which is nice.
Example using suncalc.js, the server must have CORS enabled to works this way!
(async () => {
await import('https://cdnjs.cloudflare.com/ajax/libs/suncalc/1.8.0/suncalc.min.js')
.then( () => {
let times = SunCalc.getTimes(new Date(), 51.5,-0.1);
console.log("Golden Hour today in London: " + times.goldenHour.getHours() + ':' + times.goldenHour.getMinutes() + ". Take your pics!")
})
})()
https://caniuse.com/#feat=es6-module-dynamic-import
Here is some example code I've found... does anyone have a better way?
function include(url)
{
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
var nodes = document.getElementsByTagName("*");
var node = nodes[nodes.length -1].parentNode;
node.appendChild(s);
}
If you have jQuery loaded already, you should use $.getScript.
This has an advantage over the other answers here in that you have a built in callback function (to guarantee the script is loaded before the dependant code runs) and you can control caching.
With Promises you can simplify it like this.
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 => {})
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.
If you want a SYNC script loading, you need to add script text directly to HTML HEAD element. Adding it as will trigger an ASYNC load. To load script text from external file synchronously, use XHR. Below a quick sample (it is using parts of other answers in this and other posts):
/*sample requires an additional method for array prototype:*/
if (Array.prototype.contains === undefined) {
Array.prototype.contains = function (obj) {
var i = this.length;
while (i--) { if (this[i] === obj) return true; }
return false;
};
};
/*define object that will wrap our logic*/
var ScriptLoader = {
LoadedFiles: [],
LoadFile: function (url) {
var self = this;
if (this.LoadedFiles.contains(url)) return;
var xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
self.LoadedFiles.push(url);
self.AddScript(xhr.responseText);
} else {
if (console) console.error(xhr.statusText);
}
}
};
xhr.open("GET", url, false);/*last parameter defines if call is async or not*/
xhr.send(null);
},
AddScript: function (code) {
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
};
/*Load script file. ScriptLoader will check if you try to load a file that has already been loaded (this check might be better, but I'm lazy).*/
ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
ScriptLoader.LoadFile("Scripts/jquery-2.0.1.min.js");
/*this will be executed right after upper lines. It requires jquery to execute. It requires a HTML input with id "tb1"*/
$(function () { alert($('#tb1').val()); });
does anyone have a better way?
I think just adding the script to the body would be easier then adding it to the last node on the page. How about this:
function include(url) {
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
document.body.appendChild(s);
}
i've used yet another solution i found on the net ... this one is under creativecommons and it checks if the source was included prior to calling the function ...
you can find the file here: include.js
/** include - including .js files from JS - bfults#gmail.com - 2005-02-09
** Code licensed under Creative Commons Attribution-ShareAlike License
** http://creativecommons.org/licenses/by-sa/2.0/
**/
var hIncludes = null;
function include(sURI)
{
if (document.getElementsByTagName)
{
if (!hIncludes)
{
hIncludes = {};
var cScripts = document.getElementsByTagName("script");
for (var i=0,len=cScripts.length; i < len; i++)
if (cScripts[i].src) hIncludes[cScripts[i].src] = true;
}
if (!hIncludes[sURI])
{
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.src = sURI;
hIncludes[sURI]=true;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
}
}
Just found out about a great feature in YUI 3 (at the time of writing available in preview release). You can easily insert dependencies to YUI libraries and to "external" modules (what you are looking for) without too much code: YUI Loader.
It also answers your second question regarding the function being called as soon as the external module is loaded.
Example:
YUI({
modules: {
'simple': {
fullpath: "http://example.com/public/js/simple.js"
},
'complicated': {
fullpath: "http://example.com/public/js/complicated.js"
requires: ['simple'] // <-- dependency to 'simple' module
}
},
timeout: 10000
}).use('complicated', function(Y, result) {
// called as soon as 'complicated' is loaded
if (!result.success) {
// loading failed, or timeout
handleError(result.msg);
} else {
// call a function that needs 'complicated'
doSomethingComplicated(...);
}
});
Worked perfectly for me and has the advantage of managing dependencies. Refer to the YUI documentation for an example with YUI 2 calendar.
I know my answer is bit late for this question, but, here is a great article in www.html5rocks.com - Deep dive into the murky waters of script loading .
In that article it is concluded that in regards of browser support, the best way to dynamically load JavaScript file without blocking content rendering is the following way:
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"
])
There's a new proposed ECMA standard called dynamic import, recently incorporated into Chrome and Safari.
const moduleSpecifier = './dir/someModule.js';
import(moduleSpecifier)
.then(someModule => someModule.foo()); // executes foo method in someModule
The technique we use at work is to request the javascript file using an AJAX request and then eval() the return. If you're using the prototype library, they support this functionality in their Ajax.Request call.
jquery resolved this for me with its .append() function
- used this to load the complete jquery ui package
/*
* FILENAME : project.library.js
* USAGE : loads any javascript library
*/
var dirPath = "../js/";
var library = ["functions.js","swfobject.js","jquery.jeditable.mini.js","jquery-ui-1.8.8.custom.min.js","ui/jquery.ui.core.min.js","ui/jquery.ui.widget.min.js","ui/jquery.ui.position.min.js","ui/jquery.ui.button.min.js","ui/jquery.ui.mouse.min.js","ui/jquery.ui.dialog.min.js","ui/jquery.effects.core.min.js","ui/jquery.effects.blind.min.js","ui/jquery.effects.fade.min.js","ui/jquery.effects.slide.min.js","ui/jquery.effects.transfer.min.js"];
for(var script in library){
$('head').append('<script type="text/javascript" src="' + dirPath + library[script] + '"></script>');
}
To Use - in the head of your html/php/etc after you import jquery.js you would just include this one file like so to load in the entirety of your library appending it to the head...
<script type="text/javascript" src="project.library.js"></script>
Keep it nice, short, simple, and maintainable! :]
// 3rd party plugins / script (don't forget the full path is necessary)
var FULL_PATH = '', s =
[
FULL_PATH + 'plugins/script.js' // Script example
FULL_PATH + 'plugins/jquery.1.2.js', // jQuery Library
FULL_PATH + 'plugins/crypto-js/hmac-sha1.js', // CryptoJS
FULL_PATH + 'plugins/crypto-js/enc-base64-min.js' // CryptoJS
];
function load(url)
{
var ajax = new XMLHttpRequest();
ajax.open('GET', url, false);
ajax.onreadystatechange = function ()
{
var script = ajax.response || ajax.responseText;
if (ajax.readyState === 4)
{
switch(ajax.status)
{
case 200:
eval.apply( window, [script] );
console.log("library loaded: ", url);
break;
default:
console.log("ERROR: library not loaded: ", url);
}
}
};
ajax.send(null);
}
// initialize a single load
load('plugins/script.js');
// initialize a full load of scripts
if (s.length > 0)
{
for (i = 0; i < s.length; i++)
{
load(s[i]);
}
}
This code is simply a short functional example that could require additional feature functionality for full support on any (or given) platform.
Something like this...
<script>
$(document).ready(function() {
$('body').append('<script src="https://maps.googleapis.com/maps/api/js?key=KEY&libraries=places&callback=getCurrentPickupLocation" async defer><\/script>');
});
</script>
This works:
await new Promise((resolve, reject) => {
let js = document.createElement("script");
js.src = "mylibrary.js";
js.onload = resolve;
js.onerror = reject;
document.body.appendChild(js)
});
Obviously if the script you want to import is a module, you can use the import(...) function.
There are scripts that are designed specifically for this purpose.
yepnope.js is built into Modernizr, and lab.js is a more optimized (but less user friendly version.
I wouldn't reccomend doing this through a big library like jquery or prototype - because one of the major benefits of a script loader is the ability to load scripts early - you shouldn't have to wait until jquery & all your dom elements load before running a check to see if you want to dynamically load a script.
I wrote a simple module that automatizes the job of importing/including module scripts in JavaScript. Give it a try and please spare some feedback! :) For detailed explanation of the code refer to this blog post: http://stamat.wordpress.com/2013/04/12/javascript-require-import-include-modules/
var _rmod = _rmod || {}; //require module namespace
_rmod.on_ready_fn_stack = [];
_rmod.libpath = '';
_rmod.imported = {};
_rmod.loading = {
scripts: {},
length: 0
};
_rmod.findScriptPath = function(script_name) {
var script_elems = document.getElementsByTagName('script');
for (var i = 0; i < script_elems.length; i++) {
if (script_elems[i].src.endsWith(script_name)) {
var href = window.location.href;
href = href.substring(0, href.lastIndexOf('/'));
var url = script_elems[i].src.substring(0, script_elems[i].length - script_name.length);
return url.substring(href.length+1, url.length);
}
}
return '';
};
_rmod.libpath = _rmod.findScriptPath('script.js'); //Path of your main script used to mark the root directory of your library, any library
_rmod.injectScript = function(script_name, uri, callback, prepare) {
if(!prepare)
prepare(script_name, uri);
var script_elem = document.createElement('script');
script_elem.type = 'text/javascript';
script_elem.title = script_name;
script_elem.src = uri;
script_elem.async = true;
script_elem.defer = false;
if(!callback)
script_elem.onload = function() {
callback(script_name, uri);
};
document.getElementsByTagName('head')[0].appendChild(script_elem);
};
_rmod.requirePrepare = function(script_name, uri) {
_rmod.loading.scripts[script_name] = uri;
_rmod.loading.length++;
};
_rmod.requireCallback = function(script_name, uri) {
_rmod.loading.length--;
delete _rmod.loading.scripts[script_name];
_rmod.imported[script_name] = uri;
if(_rmod.loading.length == 0)
_rmod.onReady();
};
_rmod.onReady = function() {
if (!_rmod.LOADED) {
for (var i = 0; i < _rmod.on_ready_fn_stack.length; i++){
_rmod.on_ready_fn_stack[i]();
});
_rmod.LOADED = true;
}
};
//you can rename based on your liking. I chose require, but it can be called include or anything else that is easy for you to remember or write, except import because it is reserved for future use.
var require = function(script_name) {
var np = script_name.split('.');
if (np[np.length-1] === '*') {
np.pop();
np.push('_all');
}
script_name = np.join('.');
var uri = _rmod.libpath + np.join('/')+'.js';
if (!_rmod.loading.scripts.hasOwnProperty(script_name)
&& !_rmod.imported.hasOwnProperty(script_name)) {
_rmod.injectScript(script_name, uri,
_rmod.requireCallback,
_rmod.requirePrepare);
}
};
var ready = function(fn) {
_rmod.on_ready_fn_stack.push(fn);
};
// ----- USAGE -----
require('ivar.util.array');
require('ivar.util.string');
require('ivar.net.*');
ready(function(){
//do something when required scripts are loaded
});
I am lost in all these samples but today I needed to load an external .js from my main .js and I did this:
document.write("<script src='https://www.google.com/recaptcha/api.js'></script>");
Here is a simple one with callback and IE support:
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function () {
//jQuery loaded
console.log('jquery loaded');
});
Here a simple example for a function to load JS files. Relevant points:
you don't need jQuery, so you may use this initially to load also the jQuery.js file
it is async with callback
it ensures it loads only once, as it keeps an enclosure with the record of loaded urls, thus avoiding usage of network
contrary to jQuery $.ajax or $.getScript you can use nonces, solving thus issues with CSP unsafe-inline. Just use the property script.nonce
var getScriptOnce = function() {
var scriptArray = []; //array of urls (closure)
//function to defer loading of script
return function (url, callback){
//the array doesn't have such url
if (scriptArray.indexOf(url) === -1){
var script=document.createElement('script');
script.src=url;
var head=document.getElementsByTagName('head')[0],
done=false;
script.onload=script.onreadystatechange = function(){
if ( !done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') ) {
done=true;
if (typeof callback === 'function') {
callback();
}
script.onload = script.onreadystatechange = null;
head.removeChild(script);
scriptArray.push(url);
}
};
head.appendChild(script);
}
};
}();
Now you use it simply by
getScriptOnce("url_of_your_JS_file.js");
For those of you, who love one-liners:
import('./myscript.js');
Chances are you might get an error, like:
Access to script at 'http://..../myscript.js' from origin
'http://127.0.0.1' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
In which case, you can fallback to:
fetch('myscript.js').then(r => r.text()).then(t => new Function(t)());
In as much as I love how handy the JQuery approach is, the JavaScript approach isn't that complicated but just require little tweaking to what you already use...
Here is how I load JS dynamically(Only when needed), and wait for them to load before executing the script that depends on them.
JavaScript Approach
//Create a script element that will load
let dynamicScript = document.createElement('script');
//Set source to the script we need to load
dynamicScript.src = 'linkToNeededJsFile.js';
//Set onload to callback function that depends on this script or do inline as shown below
dynamicScript.onload = () => {
//Code that depends on the loaded script should be here
};
//append the created script element to body element
document.body.append(dynamicScript);
There are other ways approach one could accomplish this with JS but, I prefer this as it's require the basic JS knowledge every dev can relate.
Not part of the answer but here is the JQuery version I prefer with projects that already include JQuery:
$.getScript('linkToNeededJsFile.js', () => {
//Code that depends on the loaded script should be here
});
More on the JQuery option here
This function uses memorization. And could be called many times with no conflicts of loading and running the same script twice. Also it's not resolving sooner than the script is actually loaded (like in #radulle answer).
const loadScript = function () {
let cache = {};
return function (src) {
return cache[src] || (cache[src] = new Promise((resolve, reject) => {
let s = document.createElement('script');
s.defer = true;
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.append(s);
}));
}
}();
Please notice the parentheses () after the function expression.
Parallel loading of scripts:
Promise.all([
loadScript('/script1.js'),
loadScript('/script2.js'),
// ...
]).then(() => {
// do something
})
You can use the same method for dynamic loading stylesheets.
all the major javascript libraries like jscript, prototype, YUI have support for loading script files. For example, in YUI, after loading the core you can do the following to load the calendar control
var loader = new YAHOO.util.YUILoader({
require: ['calendar'], // what components?
base: '../../build/',//where do they live?
//filter: "DEBUG", //use debug versions (or apply some
//some other filter?
//loadOptional: true, //load all optional dependencies?
//onSuccess is the function that YUI Loader
//should call when all components are successfully loaded.
onSuccess: function() {
//Once the YUI Calendar Control and dependencies are on
//the page, we'll verify that our target container is
//available in the DOM and then instantiate a default
//calendar into it:
YAHOO.util.Event.onAvailable("calendar_container", function() {
var myCal = new YAHOO.widget.Calendar("mycal_id", "calendar_container");
myCal.render();
})
},
// should a failure occur, the onFailure function will be executed
onFailure: function(o) {
alert("error: " + YAHOO.lang.dump(o));
}
});
// Calculate the dependency and insert the required scripts and css resources
// into the document
loader.insert();
I have tweaked some of the above post with working example.
Here we can give css and js in same array also.
$(document).ready(function(){
if (Array.prototype.contains === undefined) {
Array.prototype.contains = function (obj) {
var i = this.length;
while (i--) { if (this[i] === obj) return true; }
return false;
};
};
/* define object that will wrap our logic */
var jsScriptCssLoader = {
jsExpr : new RegExp( "js$", "i" ),
cssExpr : new RegExp( "css$", "i" ),
loadedFiles: [],
loadFile: function (cssJsFileArray) {
var self = this;
// remove duplicates with in array
cssJsFileArray.filter((item,index)=>cssJsFileArray.indexOf(item)==index)
var loadedFileArray = this.loadedFiles;
$.each(cssJsFileArray, function( index, url ) {
// if multiple arrays are loaded the check the uniqueness
if (loadedFileArray.contains(url)) return;
if( self.jsExpr.test( url ) ){
$.get(url, function(data) {
self.addScript(data);
});
}else if( self.cssExpr.test( url ) ){
$.get(url, function(data) {
self.addCss(data);
});
}
self.loadedFiles.push(url);
});
// don't load twice accross different arrays
},
addScript: function (code) {
var oNew = document.createElement("script");
oNew.type = "text/javascript";
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
},
addCss: function (code) {
var oNew = document.createElement("style");
oNew.textContent = code;
document.getElementsByTagName("head")[0].appendChild(oNew);
}
};
//jsScriptCssLoader.loadFile(["css/1.css","css/2.css","css/3.css"]);
jsScriptCssLoader.loadFile(["js/common/1.js","js/2.js","js/common/file/fileReader.js"]);
});