11

How can I find the script element associated with the currently running script? I'm only interested in scripts inside the body of the document, not in the head (or elsewhere).

Here is what I have so far, it seems to work alright. Are there any possible pitfalls I'm not thinking of?

function getCurrentScriptElement() {
    var placeholderId = 'placeholder-' + Math.random(), 
        script, placeholder;
    document.write('<br id="' + placeholderId + '">');
    placeholder = document.getElementById(placeholderId);
    script = placeholder.previousSibling;
    placeholder.parentNode.removeChild(placeholder);
    return script;
}

The use case I have in mind for this involves scripts that need to inject content at the same place in the document where the script tag occurs, so delayed loading will not be an issue. In other words I will only be calling this function in places where it's appropriate, it's not going to be exposed as part of an API or anything.

I'm also aware of the problem with document.write and XHTML, and am not worried about it in this case (so far).


My previous attempt looked something like this:

function getCurrentScriptElement() {
    var scripts = document.getElementsByTagName('script');
    return scripts[scripts.length - 1];
}

But, as we see in this question, this could easily fail if another script has been injected into the document.


I also found document.currentScript in this WHATWG spec. It doesn't seem to be widely supported at this point. There's an interesting shim for document.currentScript that takes another route... The script triggers an error, catches it, inspects the stack trace to find the URL of the script, and then finds the script element with the matching src attribute.

It's a clever solution, but it apparently won't work in IE, and it would break if several scripts use identical URLs.


Ideally, I would like to find a solution that gives the same result as the top code sample (no extra requirements), but doesn't rely on document.write. Is this possible? If not, is this code fairly safe assuming it's used correctly (only during page load, not XHTML)?


Edit: See this answer for another potential use case, and details on why a br element is used for the placeholder.

Community
  • 1
  • 1
Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141
  • 1
    If the code is running after the DOM was loaded, `document.write` will replace the whole document. – Felix Kling Jan 19 '13 at 03:16
  • It's not running after the document is loaded... see the linked question for an example of how it's running. Still editing this question. – Dagg Nabbit Jan 19 '13 at 03:17
  • Here is another related question, maybe that's what you are looking for: http://stackoverflow.com/questions/5261300/getting-the-currently-executing-dynamically-appended-script-tag. – Felix Kling Jan 19 '13 at 03:18
  • Felix, considered that, trying to avoid that. There are comments regarding that on the other question. – Dagg Nabbit Jan 19 '13 at 03:19
  • Wouldn't it make more sense to pass the placeholder to the script you're loading instead? e.g. `/path/to/myscript.js?container=placeholder-123` – Ja͢ck Jan 19 '13 at 04:01
  • @Jack, the placeholder isn't important, it's just a dummy element that I'm putting next to the last thing loaded into the DOM (presumably the script) so I can find it. I want to just be able to plop the scripts in there without putting anything in the url. Possibly some `data-` attributes later. – Dagg Nabbit Jan 19 '13 at 04:04
  • This is just a thought, not fully thought out, but what about: http://jsfiddle.net/userdude/rZVaq/ (Or: http://jsfiddle.net/userdude/rZVaq/1/) It seems to work in FF, Chrome, Opera and IE7/8/9. – Jared Farrish Jan 19 '13 at 04:21
  • 1
    @JaredFarrish see the answer to the question linked at the bottom of this question... something else firing off a JSONP request or similar before it loads could screw it up. – Dagg Nabbit Jan 19 '13 at 04:28
  • 1
    Without something along the lines of a class on the `script.placeholder` tags or whatever, it seems your stuck with `document.write`. – Jared Farrish Jan 19 '13 at 04:42
  • possible duplicate of [How may I reference the script tag that loaded the currently-executing script?](http://stackoverflow.com/questions/403967/how-may-i-reference-the-script-tag-that-loaded-the-currently-executing-script) – zzzzBov Jan 22 '13 at 21:29
  • Also related: http://stackoverflow.com/questions/6779515/can-you-select-the-script-element-that-included-the-javascript?lq=1 – zzzzBov Jan 22 '13 at 21:30
  • @zzzzBov, did you notice that I covered pretty much all of the answers to that question in this question, and explained why they were not acceptable answers? Did you notice that I am proposing a specific way to do this in my question, and am looking for insight regarding that method? The post you linked shows no attempt at original research, why not close that one and let this one be since I'm actually trying to do something productive here instead of just crying for help? – Dagg Nabbit Jan 23 '13 at 23:27
  • @zzzzBov, Anyway, I've updated the title, so it's an entirely different question now. ;) – Dagg Nabbit Jan 23 '13 at 23:47
  • @GGG, To be honest, I'd skimmed the first few lines of your question and remembered asking the same question (your first line is misleading if you're changing the question you're asking). Was it a knee-jerk reaction? certainly. Fortunately for you no one else seems to have thought that the questions were duplicates, so it remains open. – zzzzBov Jan 24 '13 at 00:40
  • Your solution seems quite smart and it should work since it follows the HTML spec. However I see obscure corner cases: the user disabled JavaScript while loading the page and enables it after loading the entire page. Then, your document.write wouldn't be a sibling to the script tag (I am not sure whether the scripts are re-executed at all in this case). Another problem might arise from Math.random and a possible conflict in the ID of your placeholder. The third obscure problem could be another script earlier in the page that overrides the document.write() method. – h2stein Mar 14 '14 at 23:04
  • Can you provide me a fiddle? I think I have a good solution to this, but I need to see if my idea fits your example. – Buzinas Apr 04 '14 at 17:59

4 Answers4

2

OP has a good solution to a tricky problem. Yet, here are few minor suggestions to improve it.

The function should use document.currentScript when available. It works well regardless of situation. Available in FireFox +4, Chrome +29, and Opera +16.

With IE 6-10 the script.readyState can be used to emulate document.currentScript. It also appears to work well regardless of situation.

To avoid problems, the function should exit when document.readyState == "complete" or even at the earlier "interactive" stage depending on the code. document.currentScript appears to stop working when "complete". However, document.write stops at "interactive", i.e., the tag writing method of finding scripts will fail with a lazy loaded script.

The script async attribute should be checked before using document.write(). Scripts marked async are detached from the document and document.write() method will not work as expected.

OP's code writes a <p> tag, but a style tag appeared to work better with scripts in both the body and head ... and without side effects.

The random number tag ID doesn't seem to serve much purpose since the element is removed. Might be better to go with a GUID constant that's reused.

UPDATE:

I've updated my code sample below to illustrate a way to do this without writing tags. It appears to work fine with Chrome, FireFox, Opera, and IE 5-10. Returns the correct value regardless of injected scripts and script attributes (defer and async). The difficult part is what to do about IE +11? Microsoft removed script.readyState in IE11 without providing an alternative. One workaround is to use "MutationObserver" to look ahead for scripts. In IE these events events happen each time before a script is executed (as opposed to onload which happens afterward). And this works well except when there are externally loaded scripts with defer or async attributes set. Async scripts are especially difficult because they can execute anytime and not always in the same order (my observation).

Anyway, I thought I'd post this code with the hope it might provide a starting point and help others out.

References:

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

http://msdn.microsoft.com/en-us/library/ie/dn265034(v=vs.85).aspx

CODE SNIPPET:

    var currentScript = (function() {

        // PRIVATE
        var observer, current, nodes;

        // IE+11 - untested with other browsers.
        if (!document.currentScript && typeof MutationObserver=='function') {
            observer = new MutationObserver(function(mutations) {
                if (document.readyState=='complete') observer.disconnect();
                mutations.forEach(function(mutation) {
                    nodes = mutation.addedNodes;
                    if (nodes.length && nodes[0].nodeName=='SCRIPT') {
                        current = nodes[0];
                    }
                });
            });
            observer.observe(window.document, {childList: true, subtree: true});
        }

        // PUBLIC
        return function() {

            if (document.readyState=='complete') return;

            // CHROME+29, FIREFOX+4, OPERA+16
            if (document.currentScript) return document.currentScript;

            var i, s=document.getElementsByTagName('SCRIPT');

            // MSIE+5-10
            if (s[0].readyState) 
                for(i=0; i<s.length; i++)
                    if (s[i].readyState=='interactive') return s[i];

            // IE+11
            if (current) return current;

            // BEST GUESS
            return s[s.length-1];

        }

    })()
Yogi
  • 6,241
  • 3
  • 24
  • 30
0

If your script isn't inline (e.g. it's loaded via ’<script src>’, Throw an error in a try block then check it's stack trace in the catch block. You'll be able to determine the file name. You can then use ’querySelector('[src="'+filename+'"]')’ to find the tag and insert your content before it or it's sibling.

PatAtCP
  • 577
  • 3
  • 10
-1

Assuming your script isn't deferred, you can just do:

var allScripts = document.getElementsByTagName("script"),
    currentScript = allScripts[allScripts.length-1];
Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
  • 4
    Please re-read the question. The OP explicitly ruled this out because it might not work correctly. – Bergi Mar 02 '14 at 15:06
-2

Just another idea: according to w3schools the <script> tag supports global HTML attributes.

Using any of the global attribute (id, class, title) You can distinguish between the script tags used, although in that case you have to pre-define the id and class.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Rehan Anis
  • 788
  • 1
  • 5
  • 7
  • 3
    While nothing wrong in your answer, it doesn't really what the question here is about. – Shadow The GPT Wizard Apr 11 '13 at 12:32
  • Added a link to another answer at the bottom of the question, have a look at that. Right now, the code there looks like: ``. Doing it with, say, an ID attribute, it would look more like this: `` ... not ideal. – Dagg Nabbit Apr 11 '13 at 14:15
  • Actually this works as well. QuerySelector('#myscript') gets you access to the he tag. Seems better than your other options – PatAtCP Mar 02 '14 at 14:54
  • I've edited your question, just to show you how to create a better looking answer. This may aid you in new questions (take a look by hitting the edit button). This one indeed does not seem to answer the question; the location of the code and from where the code is executed are two different things. It seems that this question is about the latter (but I would not call myself a JS expert). – Maarten Bodewes Feb 01 '15 at 13:07
  • thanks for the help and comments. But just please stop hitting the downward link to negate an already beaten answer. – Rehan Anis Feb 02 '15 at 12:42