Related
How can I reference the script element that loaded the javascript that is currently running?
Here's the situation. I have a "master" script being loaded high in the page, first thing under the HEAD tag.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="scripts.js"></script>
There is a script in "scripts.js" which needs to be able to do on-demand loading of other scripts. The normal method doesn't quite work for me because I need to add new scripts without referencing the HEAD tag, because the HEAD element hasn't finished rendering:
document.getElementsByTagName('head')[0].appendChild(v);
What I want to do is reference the script element that loaded the current script so that I can then append my new dynamically loaded script tags into the DOM after it.
<script type="text/javascript" src="scripts.js"></script>
loaded by scripts.js--><script type="text/javascript" src="new_script1.js"></script>
loaded by scripts.js --><script type="text/javascript" src="new_script2.js"></script>
How to get the current script element:
1. Use document.currentScript
document.currentScript will return the <script> element whose script is currently being processed.
<script>
var me = document.currentScript;
</script>
Benefits
Simple and explicit. Reliable.
Don't need to modify the script tag
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Will not work in older browsers and IE.
Does not work with modules <script type="module">
2. Select script by id
Giving the script an id attribute will let you easily select it by id from within using document.getElementById().
<script id="myscript">
var me = document.getElementById('myscript');
</script>
Benefits
Simple and explicit. Reliable.
Almost universally supported
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
id attribute may cause weird behaviour for scripts in some browsers for some edge cases
3. Select the script using a data-* attribute
Giving the script a data-* attribute will let you easily select it from within.
<script data-name="myscript">
var me = document.querySelector('script[data-name="myscript"]');
</script>
This has few benefits over the previous option.
Benefits
Simple and explicit.
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
HTML5, and querySelector() not compliant in all browsers
Less widely supported than using the id attribute
Will get around <script> with id edge cases.
May get confused if another element has the same data attribute and value on the page.
4. Select the script by src
Instead of using the data attributes, you can use the selector to choose the script by source:
<script src="//example.com/embed.js"></script>
In embed.js:
var me = document.querySelector('script[src="//example.com/embed.js"]');
Benefits
Reliable
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
No custom attributes or id needed
Problems
Does not work for local scripts
Will cause problems in different environments, like Development and Production
Static and fragile. Changing the location of the script file will require modifying the script
Less widely supported than using the id attribute
Will cause problems if you load the same script twice
5. Loop over all scripts to find the one you want
We can also loop over every script element and check each individually to select the one we want:
<script>
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
if( isMe(scripts[i])){
me = scripts[i];
}
}
</script>
This lets us use both previous techniques in older browsers that don't support querySelector() well with attributes. For example:
function isMe(scriptElem){
return scriptElem.getAttribute('src') === "//example.com/embed.js";
}
This inherits the benefits and problems of whatever approach is taken, but does not rely on querySelector() so will work in older browsers.
6. Get the last executed script
Since the scripts are executed sequentially, the last script element will very often be the currently running script:
<script>
var scripts = document.getElementsByTagName( 'script' );
var me = scripts[ scripts.length - 1 ];
</script>
Benefits
Simple.
Almost universally supported
No custom attributes or id needed
Problems
Does not work with asynchronous scripts (defer & async)
Does not work with scripts inserted dynamically
Since scripts are executed sequentially, the currently executed script tag is always the last script tag on the page until then. So, to get the script tag, you can do:
var scripts = document.getElementsByTagName( 'script' );
var thisScriptTag = scripts[ scripts.length - 1 ];
Probably the easiest thing to do would be to give your scrip tag an id attribute.
Here's a bit of a polyfill that leverages document.CurrentScript if it exists and falls back to finding the script by ID.
<script id="uniqueScriptId">
(function () {
var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');
// your code referencing thisScript here
());
</script>
If you include this at the top of every script tag I believe you'll be able to consistently know which script tag is being fired, and you'll also be able to reference the script tag in the context of an asynchronous callback.
Untested, so leave feedback for others if you try it.
Script are executed sequentially only if they do not have either a "defer" or an "async" attribute. Knowing one of the possible ID/SRC/TITLE attributes of the script tag could work also in those cases. So both Greg and Justin suggestions are correct.
There is already a proposal for a document.currentScript on the WHATWG lists.
EDIT: Firefox > 4 already implement this very useful property but it is not available in IE11 last I checked and only available in Chrome 29 and Safari 8.
EDIT: Nobody mentioned the "document.scripts" collection but I believe that the following may be a good cross browser alternative to get the currently running script:
var me = document.scripts[document.scripts.length -1];
It must works at page load and when an script tag is added with javascript (ex. with ajax)
<script id="currentScript">
var $this = document.getElementById("currentScript");
$this.setAttribute("id","");
//...
</script>
To get the script, that currently loaded the script you can use
var thisScript = document.currentScript;
You need to keep a reference at the beginning of your script, so you can call later
var url = thisScript.src
An approach for dealing with async & deferred scripts is to leverage the onload handler- set an onload handler for all script tags and the first one which executes should be yours.
function getCurrentScript(callback) {
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad() {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener('load', onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener('load', onLoad, false);
}
}
getCurrentScript(function(currentScript) {
window.console.log(currentScript.src);
});
Follow these simple steps to obtain reference to current executing script block:
Put some random unique string within the script block (must be unique / different in each script block)
Iterate result of document.getElementsByTagName('script'), looking the unique string from each of their content (obtained from innerText/textContent property).
Example (ABCDE345678 is the unique ID):
<script type="text/javascript">
var A=document.getElementsByTagName('script'),i=count(A),thi$;
for(;i;thi$=A[--i])
if((thi$.innerText||thi$.textContent).indexOf('ABCDE345678'))break;
// Now thi$ is refer to current script block
</script>
btw, for your case, you can simply use old fashioned document.write() method to include another script.
As you mentioned that DOM is not rendered yet, you can take advantage from the fact that browser always execute script in linear sequence (except for deferred one that will be rendered later), so the rest of your document is still "not exists".
Anything you write through document.write() will be placed right after the caller script.
Example of original HTML page:
<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>
Content of script.js:
document.write('<script src="inserted.js"></script>');
After rendered, the DOM structure will become:
HEAD
SCRIPT script.js
SCRIPT inserted.js
SCRIPT otherscript.js
BODY
Consider this algorithm. When your script loads (if there are multiple identical scripts), look through document.scripts, find the first script with the correct "src" attribute, and save it and mark it as 'visited' with a data-attribute or unique className.
When the next script loads, scan through document.scripts again, passing over any script already marked as visited. Take the first unvisited instance of that script.
This assumes that identical scripts will likely execute in the order in which they are loaded, from head to body, from top to bottom, from synchronous to asynchronous.
(function () {
var scripts = document.scripts;
// Scan for this data-* attribute
var dataAttr = 'data-your-attribute-here';
var i = 0;
var script;
while (i < scripts.length) {
script = scripts[i];
if (/your_script_here\.js/i.test(script.src)
&& !script.hasAttribute(dataAttr)) {
// A good match will break the loop before
// script is set to null.
break;
}
// If we exit the loop through a while condition failure,
// a check for null will reveal there are no matches.
script = null;
++i;
}
/**
* This specific your_script_here.js script tag.
* #type {Element|Node}
*/
var yourScriptVariable = null;
// Mark the script an pass it on.
if (script) {
script.setAttribute(dataAttr, '');
yourScriptVariable = script;
}
})();
This will scan through all the script for the first matching script that isn't marked with the special attribute.
Then mark that node, if found, with a data-attribute so subsequent scans won't choose it. This is similar to graph traversal BFS and DFS algorithms where nodes may be marked as 'visited' to prevent revisitng.
I've got this, which is working in FF3, IE6 & 7. The methods in the on-demand loaded scripts aren't available until page load is complete, but this is still very useful.
//handle on-demand loading of javascripts
makescript = function(url){
var v = document.createElement('script');
v.src=url;
v.type='text/javascript';
//insertAfter. Get last <script> tag in DOM
d=document.getElementsByTagName('script')[(document.getElementsByTagName('script').length-1)];
d.parentNode.insertBefore( v, d.nextSibling );
}
I was inserting script tags dynamically with this usual alternative to eval and simply set a global property currentComponentScript right before adding to the DOM.
const old = el.querySelector("script")[0];
const replacement = document.createElement("script");
replacement.setAttribute("type", "module");
replacement.appendChild(document.createTextNode(old.innerHTML));
window.currentComponentScript = replacement;
old.replaceWith(replacement);
Doesn't work in a loop though. The DOM doesn't run the scripts until the next macrotask so a batch of them will only see the last value set. You'd have to setTimeout the whole paragraph, and then setTimeout the next one after the previous finishes. I.e. chain the setTimeouts, not just call setTimeout multiple times in a row from a loop.
If you can assume the file name of the script, you can find it. I've only really tested the following function in Firefox so far.
function findMe(tag, attr, file) {
var tags = document.getElementsByTagName(tag);
var r = new RegExp(file + '$');
for (var i = 0;i < tags.length;i++) {
if (r.exec(tags[i][attr])) {
return tags[i][attr];
}
}
};
var element = findMe('script', 'src', 'scripts.js');
I have found the following code to be the most consistent, performant, and simple.
var scripts = document.getElementsByTagName('script');
var thisScript = null;
var i = scripts.length;
while (i--) {
if (scripts[i].src && (scripts[i].src.indexOf('yourscript.js') !== -1)) {
thisScript = scripts[i];
break;
}
}
console.log(thisScript);
How can I reference the script element that loaded the javascript that is currently running?
Here's the situation. I have a "master" script being loaded high in the page, first thing under the HEAD tag.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="scripts.js"></script>
There is a script in "scripts.js" which needs to be able to do on-demand loading of other scripts. The normal method doesn't quite work for me because I need to add new scripts without referencing the HEAD tag, because the HEAD element hasn't finished rendering:
document.getElementsByTagName('head')[0].appendChild(v);
What I want to do is reference the script element that loaded the current script so that I can then append my new dynamically loaded script tags into the DOM after it.
<script type="text/javascript" src="scripts.js"></script>
loaded by scripts.js--><script type="text/javascript" src="new_script1.js"></script>
loaded by scripts.js --><script type="text/javascript" src="new_script2.js"></script>
How to get the current script element:
1. Use document.currentScript
document.currentScript will return the <script> element whose script is currently being processed.
<script>
var me = document.currentScript;
</script>
Benefits
Simple and explicit. Reliable.
Don't need to modify the script tag
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Will not work in older browsers and IE.
Does not work with modules <script type="module">
2. Select script by id
Giving the script an id attribute will let you easily select it by id from within using document.getElementById().
<script id="myscript">
var me = document.getElementById('myscript');
</script>
Benefits
Simple and explicit. Reliable.
Almost universally supported
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
id attribute may cause weird behaviour for scripts in some browsers for some edge cases
3. Select the script using a data-* attribute
Giving the script a data-* attribute will let you easily select it from within.
<script data-name="myscript">
var me = document.querySelector('script[data-name="myscript"]');
</script>
This has few benefits over the previous option.
Benefits
Simple and explicit.
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
HTML5, and querySelector() not compliant in all browsers
Less widely supported than using the id attribute
Will get around <script> with id edge cases.
May get confused if another element has the same data attribute and value on the page.
4. Select the script by src
Instead of using the data attributes, you can use the selector to choose the script by source:
<script src="//example.com/embed.js"></script>
In embed.js:
var me = document.querySelector('script[src="//example.com/embed.js"]');
Benefits
Reliable
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
No custom attributes or id needed
Problems
Does not work for local scripts
Will cause problems in different environments, like Development and Production
Static and fragile. Changing the location of the script file will require modifying the script
Less widely supported than using the id attribute
Will cause problems if you load the same script twice
5. Loop over all scripts to find the one you want
We can also loop over every script element and check each individually to select the one we want:
<script>
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
if( isMe(scripts[i])){
me = scripts[i];
}
}
</script>
This lets us use both previous techniques in older browsers that don't support querySelector() well with attributes. For example:
function isMe(scriptElem){
return scriptElem.getAttribute('src') === "//example.com/embed.js";
}
This inherits the benefits and problems of whatever approach is taken, but does not rely on querySelector() so will work in older browsers.
6. Get the last executed script
Since the scripts are executed sequentially, the last script element will very often be the currently running script:
<script>
var scripts = document.getElementsByTagName( 'script' );
var me = scripts[ scripts.length - 1 ];
</script>
Benefits
Simple.
Almost universally supported
No custom attributes or id needed
Problems
Does not work with asynchronous scripts (defer & async)
Does not work with scripts inserted dynamically
Since scripts are executed sequentially, the currently executed script tag is always the last script tag on the page until then. So, to get the script tag, you can do:
var scripts = document.getElementsByTagName( 'script' );
var thisScriptTag = scripts[ scripts.length - 1 ];
Probably the easiest thing to do would be to give your scrip tag an id attribute.
Here's a bit of a polyfill that leverages document.CurrentScript if it exists and falls back to finding the script by ID.
<script id="uniqueScriptId">
(function () {
var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');
// your code referencing thisScript here
());
</script>
If you include this at the top of every script tag I believe you'll be able to consistently know which script tag is being fired, and you'll also be able to reference the script tag in the context of an asynchronous callback.
Untested, so leave feedback for others if you try it.
Script are executed sequentially only if they do not have either a "defer" or an "async" attribute. Knowing one of the possible ID/SRC/TITLE attributes of the script tag could work also in those cases. So both Greg and Justin suggestions are correct.
There is already a proposal for a document.currentScript on the WHATWG lists.
EDIT: Firefox > 4 already implement this very useful property but it is not available in IE11 last I checked and only available in Chrome 29 and Safari 8.
EDIT: Nobody mentioned the "document.scripts" collection but I believe that the following may be a good cross browser alternative to get the currently running script:
var me = document.scripts[document.scripts.length -1];
It must works at page load and when an script tag is added with javascript (ex. with ajax)
<script id="currentScript">
var $this = document.getElementById("currentScript");
$this.setAttribute("id","");
//...
</script>
To get the script, that currently loaded the script you can use
var thisScript = document.currentScript;
You need to keep a reference at the beginning of your script, so you can call later
var url = thisScript.src
An approach for dealing with async & deferred scripts is to leverage the onload handler- set an onload handler for all script tags and the first one which executes should be yours.
function getCurrentScript(callback) {
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad() {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener('load', onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener('load', onLoad, false);
}
}
getCurrentScript(function(currentScript) {
window.console.log(currentScript.src);
});
Follow these simple steps to obtain reference to current executing script block:
Put some random unique string within the script block (must be unique / different in each script block)
Iterate result of document.getElementsByTagName('script'), looking the unique string from each of their content (obtained from innerText/textContent property).
Example (ABCDE345678 is the unique ID):
<script type="text/javascript">
var A=document.getElementsByTagName('script'),i=count(A),thi$;
for(;i;thi$=A[--i])
if((thi$.innerText||thi$.textContent).indexOf('ABCDE345678'))break;
// Now thi$ is refer to current script block
</script>
btw, for your case, you can simply use old fashioned document.write() method to include another script.
As you mentioned that DOM is not rendered yet, you can take advantage from the fact that browser always execute script in linear sequence (except for deferred one that will be rendered later), so the rest of your document is still "not exists".
Anything you write through document.write() will be placed right after the caller script.
Example of original HTML page:
<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>
Content of script.js:
document.write('<script src="inserted.js"></script>');
After rendered, the DOM structure will become:
HEAD
SCRIPT script.js
SCRIPT inserted.js
SCRIPT otherscript.js
BODY
Consider this algorithm. When your script loads (if there are multiple identical scripts), look through document.scripts, find the first script with the correct "src" attribute, and save it and mark it as 'visited' with a data-attribute or unique className.
When the next script loads, scan through document.scripts again, passing over any script already marked as visited. Take the first unvisited instance of that script.
This assumes that identical scripts will likely execute in the order in which they are loaded, from head to body, from top to bottom, from synchronous to asynchronous.
(function () {
var scripts = document.scripts;
// Scan for this data-* attribute
var dataAttr = 'data-your-attribute-here';
var i = 0;
var script;
while (i < scripts.length) {
script = scripts[i];
if (/your_script_here\.js/i.test(script.src)
&& !script.hasAttribute(dataAttr)) {
// A good match will break the loop before
// script is set to null.
break;
}
// If we exit the loop through a while condition failure,
// a check for null will reveal there are no matches.
script = null;
++i;
}
/**
* This specific your_script_here.js script tag.
* #type {Element|Node}
*/
var yourScriptVariable = null;
// Mark the script an pass it on.
if (script) {
script.setAttribute(dataAttr, '');
yourScriptVariable = script;
}
})();
This will scan through all the script for the first matching script that isn't marked with the special attribute.
Then mark that node, if found, with a data-attribute so subsequent scans won't choose it. This is similar to graph traversal BFS and DFS algorithms where nodes may be marked as 'visited' to prevent revisitng.
I've got this, which is working in FF3, IE6 & 7. The methods in the on-demand loaded scripts aren't available until page load is complete, but this is still very useful.
//handle on-demand loading of javascripts
makescript = function(url){
var v = document.createElement('script');
v.src=url;
v.type='text/javascript';
//insertAfter. Get last <script> tag in DOM
d=document.getElementsByTagName('script')[(document.getElementsByTagName('script').length-1)];
d.parentNode.insertBefore( v, d.nextSibling );
}
I was inserting script tags dynamically with this usual alternative to eval and simply set a global property currentComponentScript right before adding to the DOM.
const old = el.querySelector("script")[0];
const replacement = document.createElement("script");
replacement.setAttribute("type", "module");
replacement.appendChild(document.createTextNode(old.innerHTML));
window.currentComponentScript = replacement;
old.replaceWith(replacement);
Doesn't work in a loop though. The DOM doesn't run the scripts until the next macrotask so a batch of them will only see the last value set. You'd have to setTimeout the whole paragraph, and then setTimeout the next one after the previous finishes. I.e. chain the setTimeouts, not just call setTimeout multiple times in a row from a loop.
If you can assume the file name of the script, you can find it. I've only really tested the following function in Firefox so far.
function findMe(tag, attr, file) {
var tags = document.getElementsByTagName(tag);
var r = new RegExp(file + '$');
for (var i = 0;i < tags.length;i++) {
if (r.exec(tags[i][attr])) {
return tags[i][attr];
}
}
};
var element = findMe('script', 'src', 'scripts.js');
I have found the following code to be the most consistent, performant, and simple.
var scripts = document.getElementsByTagName('script');
var thisScript = null;
var i = scripts.length;
while (i--) {
if (scripts[i].src && (scripts[i].src.indexOf('yourscript.js') !== -1)) {
thisScript = scripts[i];
break;
}
}
console.log(thisScript);
How can I reference the script element that loaded the javascript that is currently running?
Here's the situation. I have a "master" script being loaded high in the page, first thing under the HEAD tag.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="scripts.js"></script>
There is a script in "scripts.js" which needs to be able to do on-demand loading of other scripts. The normal method doesn't quite work for me because I need to add new scripts without referencing the HEAD tag, because the HEAD element hasn't finished rendering:
document.getElementsByTagName('head')[0].appendChild(v);
What I want to do is reference the script element that loaded the current script so that I can then append my new dynamically loaded script tags into the DOM after it.
<script type="text/javascript" src="scripts.js"></script>
loaded by scripts.js--><script type="text/javascript" src="new_script1.js"></script>
loaded by scripts.js --><script type="text/javascript" src="new_script2.js"></script>
How to get the current script element:
1. Use document.currentScript
document.currentScript will return the <script> element whose script is currently being processed.
<script>
var me = document.currentScript;
</script>
Benefits
Simple and explicit. Reliable.
Don't need to modify the script tag
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Will not work in older browsers and IE.
Does not work with modules <script type="module">
2. Select script by id
Giving the script an id attribute will let you easily select it by id from within using document.getElementById().
<script id="myscript">
var me = document.getElementById('myscript');
</script>
Benefits
Simple and explicit. Reliable.
Almost universally supported
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
id attribute may cause weird behaviour for scripts in some browsers for some edge cases
3. Select the script using a data-* attribute
Giving the script a data-* attribute will let you easily select it from within.
<script data-name="myscript">
var me = document.querySelector('script[data-name="myscript"]');
</script>
This has few benefits over the previous option.
Benefits
Simple and explicit.
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
HTML5, and querySelector() not compliant in all browsers
Less widely supported than using the id attribute
Will get around <script> with id edge cases.
May get confused if another element has the same data attribute and value on the page.
4. Select the script by src
Instead of using the data attributes, you can use the selector to choose the script by source:
<script src="//example.com/embed.js"></script>
In embed.js:
var me = document.querySelector('script[src="//example.com/embed.js"]');
Benefits
Reliable
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
No custom attributes or id needed
Problems
Does not work for local scripts
Will cause problems in different environments, like Development and Production
Static and fragile. Changing the location of the script file will require modifying the script
Less widely supported than using the id attribute
Will cause problems if you load the same script twice
5. Loop over all scripts to find the one you want
We can also loop over every script element and check each individually to select the one we want:
<script>
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
if( isMe(scripts[i])){
me = scripts[i];
}
}
</script>
This lets us use both previous techniques in older browsers that don't support querySelector() well with attributes. For example:
function isMe(scriptElem){
return scriptElem.getAttribute('src') === "//example.com/embed.js";
}
This inherits the benefits and problems of whatever approach is taken, but does not rely on querySelector() so will work in older browsers.
6. Get the last executed script
Since the scripts are executed sequentially, the last script element will very often be the currently running script:
<script>
var scripts = document.getElementsByTagName( 'script' );
var me = scripts[ scripts.length - 1 ];
</script>
Benefits
Simple.
Almost universally supported
No custom attributes or id needed
Problems
Does not work with asynchronous scripts (defer & async)
Does not work with scripts inserted dynamically
Since scripts are executed sequentially, the currently executed script tag is always the last script tag on the page until then. So, to get the script tag, you can do:
var scripts = document.getElementsByTagName( 'script' );
var thisScriptTag = scripts[ scripts.length - 1 ];
Probably the easiest thing to do would be to give your scrip tag an id attribute.
Here's a bit of a polyfill that leverages document.CurrentScript if it exists and falls back to finding the script by ID.
<script id="uniqueScriptId">
(function () {
var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');
// your code referencing thisScript here
());
</script>
If you include this at the top of every script tag I believe you'll be able to consistently know which script tag is being fired, and you'll also be able to reference the script tag in the context of an asynchronous callback.
Untested, so leave feedback for others if you try it.
Script are executed sequentially only if they do not have either a "defer" or an "async" attribute. Knowing one of the possible ID/SRC/TITLE attributes of the script tag could work also in those cases. So both Greg and Justin suggestions are correct.
There is already a proposal for a document.currentScript on the WHATWG lists.
EDIT: Firefox > 4 already implement this very useful property but it is not available in IE11 last I checked and only available in Chrome 29 and Safari 8.
EDIT: Nobody mentioned the "document.scripts" collection but I believe that the following may be a good cross browser alternative to get the currently running script:
var me = document.scripts[document.scripts.length -1];
It must works at page load and when an script tag is added with javascript (ex. with ajax)
<script id="currentScript">
var $this = document.getElementById("currentScript");
$this.setAttribute("id","");
//...
</script>
To get the script, that currently loaded the script you can use
var thisScript = document.currentScript;
You need to keep a reference at the beginning of your script, so you can call later
var url = thisScript.src
An approach for dealing with async & deferred scripts is to leverage the onload handler- set an onload handler for all script tags and the first one which executes should be yours.
function getCurrentScript(callback) {
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad() {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener('load', onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener('load', onLoad, false);
}
}
getCurrentScript(function(currentScript) {
window.console.log(currentScript.src);
});
Follow these simple steps to obtain reference to current executing script block:
Put some random unique string within the script block (must be unique / different in each script block)
Iterate result of document.getElementsByTagName('script'), looking the unique string from each of their content (obtained from innerText/textContent property).
Example (ABCDE345678 is the unique ID):
<script type="text/javascript">
var A=document.getElementsByTagName('script'),i=count(A),thi$;
for(;i;thi$=A[--i])
if((thi$.innerText||thi$.textContent).indexOf('ABCDE345678'))break;
// Now thi$ is refer to current script block
</script>
btw, for your case, you can simply use old fashioned document.write() method to include another script.
As you mentioned that DOM is not rendered yet, you can take advantage from the fact that browser always execute script in linear sequence (except for deferred one that will be rendered later), so the rest of your document is still "not exists".
Anything you write through document.write() will be placed right after the caller script.
Example of original HTML page:
<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>
Content of script.js:
document.write('<script src="inserted.js"></script>');
After rendered, the DOM structure will become:
HEAD
SCRIPT script.js
SCRIPT inserted.js
SCRIPT otherscript.js
BODY
Consider this algorithm. When your script loads (if there are multiple identical scripts), look through document.scripts, find the first script with the correct "src" attribute, and save it and mark it as 'visited' with a data-attribute or unique className.
When the next script loads, scan through document.scripts again, passing over any script already marked as visited. Take the first unvisited instance of that script.
This assumes that identical scripts will likely execute in the order in which they are loaded, from head to body, from top to bottom, from synchronous to asynchronous.
(function () {
var scripts = document.scripts;
// Scan for this data-* attribute
var dataAttr = 'data-your-attribute-here';
var i = 0;
var script;
while (i < scripts.length) {
script = scripts[i];
if (/your_script_here\.js/i.test(script.src)
&& !script.hasAttribute(dataAttr)) {
// A good match will break the loop before
// script is set to null.
break;
}
// If we exit the loop through a while condition failure,
// a check for null will reveal there are no matches.
script = null;
++i;
}
/**
* This specific your_script_here.js script tag.
* #type {Element|Node}
*/
var yourScriptVariable = null;
// Mark the script an pass it on.
if (script) {
script.setAttribute(dataAttr, '');
yourScriptVariable = script;
}
})();
This will scan through all the script for the first matching script that isn't marked with the special attribute.
Then mark that node, if found, with a data-attribute so subsequent scans won't choose it. This is similar to graph traversal BFS and DFS algorithms where nodes may be marked as 'visited' to prevent revisitng.
I've got this, which is working in FF3, IE6 & 7. The methods in the on-demand loaded scripts aren't available until page load is complete, but this is still very useful.
//handle on-demand loading of javascripts
makescript = function(url){
var v = document.createElement('script');
v.src=url;
v.type='text/javascript';
//insertAfter. Get last <script> tag in DOM
d=document.getElementsByTagName('script')[(document.getElementsByTagName('script').length-1)];
d.parentNode.insertBefore( v, d.nextSibling );
}
I was inserting script tags dynamically with this usual alternative to eval and simply set a global property currentComponentScript right before adding to the DOM.
const old = el.querySelector("script")[0];
const replacement = document.createElement("script");
replacement.setAttribute("type", "module");
replacement.appendChild(document.createTextNode(old.innerHTML));
window.currentComponentScript = replacement;
old.replaceWith(replacement);
Doesn't work in a loop though. The DOM doesn't run the scripts until the next macrotask so a batch of them will only see the last value set. You'd have to setTimeout the whole paragraph, and then setTimeout the next one after the previous finishes. I.e. chain the setTimeouts, not just call setTimeout multiple times in a row from a loop.
If you can assume the file name of the script, you can find it. I've only really tested the following function in Firefox so far.
function findMe(tag, attr, file) {
var tags = document.getElementsByTagName(tag);
var r = new RegExp(file + '$');
for (var i = 0;i < tags.length;i++) {
if (r.exec(tags[i][attr])) {
return tags[i][attr];
}
}
};
var element = findMe('script', 'src', 'scripts.js');
I have found the following code to be the most consistent, performant, and simple.
var scripts = document.getElementsByTagName('script');
var thisScript = null;
var i = scripts.length;
while (i--) {
if (scripts[i].src && (scripts[i].src.indexOf('yourscript.js') !== -1)) {
thisScript = scripts[i];
break;
}
}
console.log(thisScript);
How can I reference the script element that loaded the javascript that is currently running?
Here's the situation. I have a "master" script being loaded high in the page, first thing under the HEAD tag.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript" src="scripts.js"></script>
There is a script in "scripts.js" which needs to be able to do on-demand loading of other scripts. The normal method doesn't quite work for me because I need to add new scripts without referencing the HEAD tag, because the HEAD element hasn't finished rendering:
document.getElementsByTagName('head')[0].appendChild(v);
What I want to do is reference the script element that loaded the current script so that I can then append my new dynamically loaded script tags into the DOM after it.
<script type="text/javascript" src="scripts.js"></script>
loaded by scripts.js--><script type="text/javascript" src="new_script1.js"></script>
loaded by scripts.js --><script type="text/javascript" src="new_script2.js"></script>
How to get the current script element:
1. Use document.currentScript
document.currentScript will return the <script> element whose script is currently being processed.
<script>
var me = document.currentScript;
</script>
Benefits
Simple and explicit. Reliable.
Don't need to modify the script tag
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Will not work in older browsers and IE.
Does not work with modules <script type="module">
2. Select script by id
Giving the script an id attribute will let you easily select it by id from within using document.getElementById().
<script id="myscript">
var me = document.getElementById('myscript');
</script>
Benefits
Simple and explicit. Reliable.
Almost universally supported
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
id attribute may cause weird behaviour for scripts in some browsers for some edge cases
3. Select the script using a data-* attribute
Giving the script a data-* attribute will let you easily select it from within.
<script data-name="myscript">
var me = document.querySelector('script[data-name="myscript"]');
</script>
This has few benefits over the previous option.
Benefits
Simple and explicit.
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
Problems
Requires adding a custom attribute to the script tag
HTML5, and querySelector() not compliant in all browsers
Less widely supported than using the id attribute
Will get around <script> with id edge cases.
May get confused if another element has the same data attribute and value on the page.
4. Select the script by src
Instead of using the data attributes, you can use the selector to choose the script by source:
<script src="//example.com/embed.js"></script>
In embed.js:
var me = document.querySelector('script[src="//example.com/embed.js"]');
Benefits
Reliable
Works with asynchronous scripts (defer & async)
Works with scripts inserted dynamically
No custom attributes or id needed
Problems
Does not work for local scripts
Will cause problems in different environments, like Development and Production
Static and fragile. Changing the location of the script file will require modifying the script
Less widely supported than using the id attribute
Will cause problems if you load the same script twice
5. Loop over all scripts to find the one you want
We can also loop over every script element and check each individually to select the one we want:
<script>
var me = null;
var scripts = document.getElementsByTagName("script")
for (var i = 0; i < scripts.length; ++i) {
if( isMe(scripts[i])){
me = scripts[i];
}
}
</script>
This lets us use both previous techniques in older browsers that don't support querySelector() well with attributes. For example:
function isMe(scriptElem){
return scriptElem.getAttribute('src') === "//example.com/embed.js";
}
This inherits the benefits and problems of whatever approach is taken, but does not rely on querySelector() so will work in older browsers.
6. Get the last executed script
Since the scripts are executed sequentially, the last script element will very often be the currently running script:
<script>
var scripts = document.getElementsByTagName( 'script' );
var me = scripts[ scripts.length - 1 ];
</script>
Benefits
Simple.
Almost universally supported
No custom attributes or id needed
Problems
Does not work with asynchronous scripts (defer & async)
Does not work with scripts inserted dynamically
Since scripts are executed sequentially, the currently executed script tag is always the last script tag on the page until then. So, to get the script tag, you can do:
var scripts = document.getElementsByTagName( 'script' );
var thisScriptTag = scripts[ scripts.length - 1 ];
Probably the easiest thing to do would be to give your scrip tag an id attribute.
Here's a bit of a polyfill that leverages document.CurrentScript if it exists and falls back to finding the script by ID.
<script id="uniqueScriptId">
(function () {
var thisScript = document.CurrentScript || document.getElementByID('uniqueScriptId');
// your code referencing thisScript here
());
</script>
If you include this at the top of every script tag I believe you'll be able to consistently know which script tag is being fired, and you'll also be able to reference the script tag in the context of an asynchronous callback.
Untested, so leave feedback for others if you try it.
Script are executed sequentially only if they do not have either a "defer" or an "async" attribute. Knowing one of the possible ID/SRC/TITLE attributes of the script tag could work also in those cases. So both Greg and Justin suggestions are correct.
There is already a proposal for a document.currentScript on the WHATWG lists.
EDIT: Firefox > 4 already implement this very useful property but it is not available in IE11 last I checked and only available in Chrome 29 and Safari 8.
EDIT: Nobody mentioned the "document.scripts" collection but I believe that the following may be a good cross browser alternative to get the currently running script:
var me = document.scripts[document.scripts.length -1];
It must works at page load and when an script tag is added with javascript (ex. with ajax)
<script id="currentScript">
var $this = document.getElementById("currentScript");
$this.setAttribute("id","");
//...
</script>
To get the script, that currently loaded the script you can use
var thisScript = document.currentScript;
You need to keep a reference at the beginning of your script, so you can call later
var url = thisScript.src
An approach for dealing with async & deferred scripts is to leverage the onload handler- set an onload handler for all script tags and the first one which executes should be yours.
function getCurrentScript(callback) {
if (document.currentScript) {
callback(document.currentScript);
return;
}
var scripts = document.scripts;
function onLoad() {
for (var i = 0; i < scripts.length; ++i) {
scripts[i].removeEventListener('load', onLoad, false);
}
callback(event.target);
}
for (var i = 0; i < scripts.length; ++i) {
scripts[i].addEventListener('load', onLoad, false);
}
}
getCurrentScript(function(currentScript) {
window.console.log(currentScript.src);
});
Follow these simple steps to obtain reference to current executing script block:
Put some random unique string within the script block (must be unique / different in each script block)
Iterate result of document.getElementsByTagName('script'), looking the unique string from each of their content (obtained from innerText/textContent property).
Example (ABCDE345678 is the unique ID):
<script type="text/javascript">
var A=document.getElementsByTagName('script'),i=count(A),thi$;
for(;i;thi$=A[--i])
if((thi$.innerText||thi$.textContent).indexOf('ABCDE345678'))break;
// Now thi$ is refer to current script block
</script>
btw, for your case, you can simply use old fashioned document.write() method to include another script.
As you mentioned that DOM is not rendered yet, you can take advantage from the fact that browser always execute script in linear sequence (except for deferred one that will be rendered later), so the rest of your document is still "not exists".
Anything you write through document.write() will be placed right after the caller script.
Example of original HTML page:
<!doctype html>
<html><head>
<script src="script.js"></script>
<script src="otherscript.js"></script>
<body>anything</body></html>
Content of script.js:
document.write('<script src="inserted.js"></script>');
After rendered, the DOM structure will become:
HEAD
SCRIPT script.js
SCRIPT inserted.js
SCRIPT otherscript.js
BODY
Consider this algorithm. When your script loads (if there are multiple identical scripts), look through document.scripts, find the first script with the correct "src" attribute, and save it and mark it as 'visited' with a data-attribute or unique className.
When the next script loads, scan through document.scripts again, passing over any script already marked as visited. Take the first unvisited instance of that script.
This assumes that identical scripts will likely execute in the order in which they are loaded, from head to body, from top to bottom, from synchronous to asynchronous.
(function () {
var scripts = document.scripts;
// Scan for this data-* attribute
var dataAttr = 'data-your-attribute-here';
var i = 0;
var script;
while (i < scripts.length) {
script = scripts[i];
if (/your_script_here\.js/i.test(script.src)
&& !script.hasAttribute(dataAttr)) {
// A good match will break the loop before
// script is set to null.
break;
}
// If we exit the loop through a while condition failure,
// a check for null will reveal there are no matches.
script = null;
++i;
}
/**
* This specific your_script_here.js script tag.
* #type {Element|Node}
*/
var yourScriptVariable = null;
// Mark the script an pass it on.
if (script) {
script.setAttribute(dataAttr, '');
yourScriptVariable = script;
}
})();
This will scan through all the script for the first matching script that isn't marked with the special attribute.
Then mark that node, if found, with a data-attribute so subsequent scans won't choose it. This is similar to graph traversal BFS and DFS algorithms where nodes may be marked as 'visited' to prevent revisitng.
I've got this, which is working in FF3, IE6 & 7. The methods in the on-demand loaded scripts aren't available until page load is complete, but this is still very useful.
//handle on-demand loading of javascripts
makescript = function(url){
var v = document.createElement('script');
v.src=url;
v.type='text/javascript';
//insertAfter. Get last <script> tag in DOM
d=document.getElementsByTagName('script')[(document.getElementsByTagName('script').length-1)];
d.parentNode.insertBefore( v, d.nextSibling );
}
I was inserting script tags dynamically with this usual alternative to eval and simply set a global property currentComponentScript right before adding to the DOM.
const old = el.querySelector("script")[0];
const replacement = document.createElement("script");
replacement.setAttribute("type", "module");
replacement.appendChild(document.createTextNode(old.innerHTML));
window.currentComponentScript = replacement;
old.replaceWith(replacement);
Doesn't work in a loop though. The DOM doesn't run the scripts until the next macrotask so a batch of them will only see the last value set. You'd have to setTimeout the whole paragraph, and then setTimeout the next one after the previous finishes. I.e. chain the setTimeouts, not just call setTimeout multiple times in a row from a loop.
If you can assume the file name of the script, you can find it. I've only really tested the following function in Firefox so far.
function findMe(tag, attr, file) {
var tags = document.getElementsByTagName(tag);
var r = new RegExp(file + '$');
for (var i = 0;i < tags.length;i++) {
if (r.exec(tags[i][attr])) {
return tags[i][attr];
}
}
};
var element = findMe('script', 'src', 'scripts.js');
I have found the following code to be the most consistent, performant, and simple.
var scripts = document.getElementsByTagName('script');
var thisScript = null;
var i = scripts.length;
while (i--) {
if (scripts[i].src && (scripts[i].src.indexOf('yourscript.js') !== -1)) {
thisScript = scripts[i];
break;
}
}
console.log(thisScript);
I have the issue of loading the same scripts across all my pages without having to define them all over and over.
I've came up with a half complete solution which looks like...
var scriptLocs = [];
function loadScripts() {
for (var i = 0; i < scriptLocs.length; i++) {
var crtElement = document.createElement('script');
crtElement.src = scriptLocs[i];
var hh = document.getElementsByTagName('head')[0];
// Getting head tag
hh.appendChild(crtElement);
//Adding it to head
}
}
The array can be very big e.g. around 120 scripts (2.5MB) these include such things as jQuery and Knockout etc...
The problem im having is this can be unreliable my function seems to miss out, or load in a different order than defined in my array. The order is very important as some things may rely on jQuery so it must be loaded before other scripts.
Also its differnt errors on every refresh ... E.g. maybe differnt loading order?
Is there away of define all the scripts in an element then appending the lot of them at once? would this work?
UPDATE:
Using this test.js file
for (var i = 0; i < scriptLocs.length; i++) {
document.write("<script type=\"text/javascript\" src=\"" + scriptLocs[i] + "\"><\/script>");
}
And including it as the very first thing in the head like..
<!DOCTYPE HTML>
<html>
<head>
<title>Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="test.js"></script>
Seems to solve this problem
with that many scripts you should maybe consider compressing and concatenating them and serve them as one minified script.
You could do this with Googles closure compiler or the YUI compressor for example:
This is what document.write() is for. Frequently it is misused, but in this case, it's a good choice:
var scriptLocs = [...];
for (var i = 0; i < scriptLocs.length; i++) {
document.write("<script type=\"text/javascript\" src=\""+scriptLocs[i]+"\"><\/script>");
}
But do not put this in a function or call it from an event handler. This script should run immediately while the page is loading. Calling document.write() after the dom has loaded causes problems.
It sounds like you are looking for a way to declare dependencies and specify loading order which is something that require js handles quite well. Additionally,
RequireJS has an optimization tool that ... [c]ombines
related scripts together into build layers and minifies them via
UglifyJS (the default) or Closure Compiler (an option when using
Java).
This would allow you to to have a single minified script to include across all pages in your application.
did you check the sizes of the scripts you are calling ?
the array will follow you order and element creation will follow your order, but remember you are calling them in javascript, so it'd never wait for a script to load and then load the other one, i.e. if you are loading jquery which is 100 kb for example and you are calling some native script which is 10 kb, so your script will be loaded first this the jquery.
you add attribute async to make the dom don't wait for your scripts.