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);
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);
Here is the JS code:
var wrap = document.createElement("div");
wrap.innerHTML = '<script type="text/javascript" src="'+scriptUrl+'"></script>';
var wrapscript = wrap.childNodes[0];
document.body.appendChild(wrapscript)
The body did insert the script element, but the JS resource wasn't loaded, there isn't even an http request.
Could someone explain why this is happening?
The problem is with Zeptojs's $ method
$('<script type="text/javascript" src="'+scriptUrl+'"></script>').appendTo($("bdoy"))
It works like the code above, and causes the bug.
This one was trivial.
As stated in spec (8.4 Parsing HTML fragments and 8.2.3.5 Other parsing state flags,) quote:
when using innerHTML the browser will
Create a new Document node, and mark it as being an HTML document.
If there is a context element, and the Document of the context element is in quirks mode, then let the Document be in quirks mode.
Otherwise, if there is a context element, and the Document of the
context element is in limited-quirks mode, then let the Document be in
limited-quirks mode. Otherwise, leave the Document in no-quirks mode.
Create a new HTML parser, and associate it with the just created Document node.
...
and when parsing a <script> inside
The scripting flag is set to "enabled" if scripting was enabled for
the Document with which the parser is associated when the parser was
created, and "disabled" otherwise.
The scripting flag can be enabled even when the parser was originally
created for the HTML fragment parsing algorithm, even though script
elements don't execute in that case.
So it won't be executed, as long as you inject it with innerHTML.
And using innerHTML will prevent the <script> element created from being executed permanently.
As stated in spec (4.3.1 The script element,) quote:
Changing the src, type, charset, async, and defer attributes dynamically has no direct effect; these attribute are only used at specific times described below.
Concluding the described below is that, it only parse the src attribute when injecting the <script> to the document (no matter which, including the temporary one created when using innerHTML.)
So, as long as you want to inject a script to the document and make it executed, you have to use script = document.createElement('script').
Set its attributes like src and type, possibly the contents inside (by using script.appendChild(document.createTextNode(content))), then append it to the document.body.
You can try this instead:
var wrap = document.createElement('div');
var scr = document.createElement('script');
scr.src = scriptUrl;
scr.type = 'text/javascript';
wrap.appendChild(scr);
document.body.appendChild(wrap);
By creating the script tag explicitly you're telling JS that the innerHTML is not a text but instead it's an executable script.
A possible solution, when you don't have control over the insertion mechanism and you are forced to use innerHTML with script beacons, is to rebuild DOM Nodes from the "ghost" ones.
This is a recurring problem in the ad-tech industry, in a which many automated systems duplicate arbitrary HTML code (aka. adservers ^^).
works fine in Chrome:
var s = wrap.getElementsByTagName('script');
for (var i = 0; i < s.length ; i++) {
var node=s[i], parent=node.parentElement, d = document.createElement('script');
d.async=node.async;
d.src=node.src;
parent.insertBefore(d,node);
parent.removeChild(node);
}
(you can test it in JSFiddle)
I am trying to load the statcounter script from my custom js file. The original script looks like this:
<html>
<head>...</head>
<body>
...
<script type="text/javascript">
var sc_project=11111111;
var sc_invisible=1;
var sc_partition=11111111;
var sc_click_stat=1;
var sc_security="11111111";
</script><script type="text/javascript" src="http://www.statcounter.com/counter/counter_xhtml.js"></script>
...
</body></html>
The code seems to set the variables, then loads the counter script which reads the values of the variables and does its job.
I'm trying to call the counter script like this:
// file: counters.js
function CounterFromStatCounter() {
sc_project=11111111;
sc_invisible=1;
sc_partition=11111111;
sc_click_stat=1;
sc_security="11111111";
var oHead = document.getElementsByTagName('HEAD').item(0);
var oScript= document.createElement("script");
oScript.type = "text/javascript";
oScript.src="http://www.statcounter.com/counter/counter_xhtml.js";
oHead.appendChild( oScript);
}
// main page
<html>
<head>
...
<script type="text/javascript" src="counters.js"></script>
...
</head>
<body>
...
<script type="text/javascript">
CounterFromStatCounter();
</script>
...
</body></html>
The code seems to work: the script element that references the statcounter script appears in the head section as it should, but no visits are recorded - this means that the variables set in my script cannot be accesed by the counter script.
What am I doing wrong?
You are doing a couple of things wrong.
1) You likely have not validated your code. Go to http://jslint.com and validate your JavaScript.
2) Do not write JavaScript into your HTML. That has a tendency to force all code bits into the global namespace, which is very likely to produce collisions with any other JavaScript code.
3) Only reference external JavaScript files directly prior to the closing body tag. Script interpretation blocks parallel downloads in IE.
Accomplish those three and then come back for more help.
var CounterFromStatCounter = function () {
var sc_project = 11111111,
sc_invisible = 1,
sc_partition = 11111111,
sc_click_stat = 1,
sc_security = "11111111",
oHead = document.getElementsByTagName('head').item(0),
oScript= document.createElement("script");
oScript.setAttribute("type", "text/javascript");
oScript.setAttribute("src", "http://www.statcounter.com/counter/counter_xhtml.js");
oHead.appendChild(oScript);
}
I have looked at the above code more closely and here are my thoughts:
1) That is how the code should look once beautified and reduced to a single var command in your function without any implied globals, except for the function name itself.
2) Dynamically created content from client-side code is destroyed each time the page loads at each user. So you will likely not want to write output using JavaScript as any means of providing a data reference point. I recommend doing this completely on the server side to be more efficient. If you must use JavaScript you will need to write to some intermediate data store, like a JSON, file that you connect to using the xmlHttpRequest object.
3) I would not recommend writing anything to the head of the document dynamically from the client-side due to different interpretations of the DOM between browsers and also once the head is loaded the browser has no reason to read it again for new information.
4) To be most efficient scripts should be in external files that referenced just before the closing body tag, because script interpretation blocks parallel downloads in IE. Putting scripts in the head is results in dramatically slower page loads in IE as a result.
5) I changed "HEAD" to "head" because JavaScript and XHTML are both case sensitive.
6) I also changed the way attributes are appended to your dynamically created script tag to use DOM methods. I don't know if this is the more correct method, but it is certainly more inline to the standards.
Define the variables globaly and write some js like below.
window.attachEvent('onload', function() {
document.write('<script type=text\/javascript src=blabla.com\/counter.js><\/sc' + 'ript>');
});
this should work in IE. For other other browsers implement addEventListener...