How may I reference the script tag that loaded the currently-executing script?
Asked Answered
S

14

387

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>
Sacristy answered 31/12, 2008 at 20:22 Comment(2)
A word of warning: modifying the DOM while it is still loading will cause you a world of hurt in IE6 & IE7. You're gonna be better off running that code after page load.Dethrone
Looks like it's up on caniuse now: caniuse.com/#feat=document-currentscriptBerkman
U
841

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
Ursulaursulette answered 30/3, 2014 at 15:2 Comment(15)
Thanks guys, but you know I answered 4 years after the accepted answer, right :)Ursulaursulette
@Josh are you trying it on the console? It's expected to be null in that case. Otherwise, that's a bug against Waterfox. Try this test page: fractallambda.com/misc/testDocumentCurrentScriptUrsulaursulette
If you're willing to ditch IE support, you can probably express #6 even more succinctly as document.querySelector('script:last-child').remove()Glochidium
This is a great answer. I would like to point out that according to the HTML5 standard, id is a global attribute and may be specified on all HTML elements, including <script>. w3.org/TR/html5/dom.html#global-attributesGastrology
Great answer... should be answer... and StackOverflow should have a mechanism to mark the best answer... right in top of page (a star flashing with the title), so we can click on it and scrool to the answer that were marked by several users as the best!!!Surgy
@ChristopherWeiss I stand corrected! Can't remember where I read that id attributes would be problematic on scripts inside the <head>. Probably a renderer-specific issue, rather than spec issue.Ursulaursulette
"document.currentScript" doesn't work for me with dynamic loaded scripts, returns null in the latest chrome/firefox, "last executed script" works okGreco
doesn't work when script is in a template inserted in a Shadow DOMEverhart
Which option should I use? I'd like to pass some parameters with data-* attributes to the script. Now I'm wondering if this is a good idea. Is there not jQuery option that does the magic?Christcrossrow
As of 2019-Oct, document.currentScript still returns null in latest Chrome.Goad
@Goad - Are you trying this in the console? This is expected to return null. Try it by embedding the script on a test page to get the right behaviour in chrome. Try this test page: fractallambda.com/misc/testDocumentCurrentScriptUrsulaursulette
@Ursulaursulette - Of course. You are sooo right. my duh. Thanks jbGoad
document.currentScript returns null in a shadow DOM in Chrome today.'Wayland
For those using option #2 with jQuery, note that $("#myscript") does not work while the script is executing but you can use $(document.getElementById('myscript'))Commensurate
If it's type="module", import.meta.url can make "select by src" approach more reliable. However, it still suffer the problem when a file.js src in many <script> tag.Jobyna
P
90

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 ];
Prissie answered 24/7, 2010 at 19:29 Comment(10)
This is simple and elegant. There's an example of it in the new Google Charts/Visualizations API if you unpack the javascript. They load JSON data from within the script tag, see: ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.jsHeyerdahl
This is a great idea, and it normally works for me. But I should add that there are times when I've found it returning a reference to a different script. Not sure why - haven't been able to track that down. Consequently, I usually go with a different method, e.g., I hard-code the name of the script file, and look for the script tag with that file name.Verein
One instance that I can think of where this might return incorrect results is when a script tag is added to the DOM asynchronously.Prissie
Yes this can have unpredictable results so you could try using a selector instead: $('script[src*="/mysource.js"]') ???Rhizoid
It doesn't work when you have scripts loaded after page loaded. You probably won't get the right tag.Talbott
How to make it work when the script loads in an async way? I am looking to get the GET parameters.Hypoglossal
Note this thing works if you query for self directly in the script main scope. If you do it in a method, other scripts may get loaded in the meantime and distort this.Ito
Since Javascript is often event based then any of the scripts in the array have an equal chance of being the currently executing script, don't they?Tahmosh
I have found that this does not always work. It is better to add a reverse find script instead. 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; } }Antiphony
WARNING: this is true only if you don't use asynch or deferTyra
P
17

Probably the easiest thing to do would be to give your scrip tag an id attribute.

Petrochemical answered 31/12, 2008 at 20:29 Comment(1)
Although you are right, there are lot's of cases in which the OP's question are valid, a couple would be: 1) when you're crawling 2) when you are working with the DOM of a client, and he is unwilling to changeShikari
F
12

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];
Felton answered 12/9, 2010 at 16:56 Comment(2)
It's document.scripts not document.scriptPuerperal
document.scripts didn't exist in FF4 we discovered when some tests failed. So you are safer using document.getElementsByTagName('script') if you want old browser support.Composed
C
12

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.

Colwell answered 13/9, 2013 at 15:29 Comment(2)
The id attribute is invalid in a script element though. What sorts of problems could this approach generate?Alley
@Alley - No, all elements can have an id attribute. id, class, and slot are defined at the DOM level, not the HTML level. If you go to the global attributes in HTML and scroll past the list, you'll find "DOM standard defines the user agent requirements for the class, id, and slot attributes for any element in any namespace." followed by "The class, id, and slot attributes may be specified on all HTML elements." The DOM spec covers it here.Enact
R
8

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>
Rover answered 18/10, 2011 at 0:59 Comment(0)
P
6

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
Patina answered 25/2, 2018 at 2:24 Comment(0)
M
5

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);
});
Mack answered 6/8, 2013 at 21:32 Comment(0)
A
4

Follow these simple steps to obtain reference to current executing script block:

  1. Put some random unique string within the script block (must be unique / different in each script block)
  2. 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
Arcane answered 26/2, 2012 at 7:44 Comment(1)
This seems to work only for inline scripts, not for external scripts. In the latter case all properties innerText, text, and textContent are empty.Fireplug
C
3

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.

Corunna answered 12/6, 2017 at 18:43 Comment(2)
Welcome to Stack Overflow. Would you care to include some code with the algorithm?Nonplus
Thar you go, @NonplusCorunna
S
1

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 );
}
Sacristy answered 31/12, 2008 at 21:12 Comment(0)
S
0

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');
Speciation answered 11/11, 2009 at 2:5 Comment(1)
Very outdated. This can be done with the querySelector, a simple oneliner!Blau
P
0

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.

Pastime answered 8/9, 2020 at 0:15 Comment(0)
A
-1

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);
Antiphony answered 20/8, 2018 at 18:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.