53

I have a page that dynamically adds script references via jQuery's $.getScript function. The scripts load and execute fine, so I know the references are correct. However, when I add a "debugger" statement to any of the scripts to allow me to step through the code in a debugger (such as VS.Net, Firebug, etc.), it doesn't work. It appears that something about the way jQuery loads the scripts is preventing debuggers from finding the files.

Does anybody have a work-around for this?

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
James Messinger
  • 3,223
  • 3
  • 25
  • 16
  • 1
    To anyone coming here from Google, the accepted answer to this question helped me debug dynamically loaded scripts in Chrome: https://stackoverflow.com/questions/9092125/how-to-debug-dynamically-loaded-javascriptwith-jquery-in-the-browsers-debugge – Cave Johnson Aug 04 '17 at 21:57

9 Answers9

77

Ok, so it turns out that the default implementation of the $.getScript() function works differently depending on whether the referenced script file is on the same domain or not. External references such as:

$.getScript("http://www.someothersite.com/script.js")

will cause jQuery to create an external script reference, which can be debugged with no problems.

<script type="text/javascript" src="http://www.someothersite.com/script.js"></script>

However, if you reference a local script file such as any of the following:

$.getScript("http://www.mysite.com/script.js")
$.getScript("script.js")
$.getScript("/Scripts/script.js");

then jQuery will download the script content asynchronously and then add it as inline content:

<script type="text/javascript">{your script here}</script>

This latter approach does not work with any debugger that I tested (Visual Studio.net, Firebug, IE8 Debugger).

The workaround is to override the $.getScript() function so that it always creates an external reference rather than inline content. Here is the script to do that. I have tested this in Firefox, Opera, Safari, and IE 8.

<script type="text/javascript">
// Replace the normal jQuery getScript function with one that supports
// debugging and which references the script files as external resources
// rather than inline.
jQuery.extend({
   getScript: function(url, callback) {
      var head = document.getElementsByTagName("head")[0];
      var script = document.createElement("script");
      script.src = url;

      // Handle Script loading
      {
         var done = false;

         // Attach handlers for all browsers
         script.onload = script.onreadystatechange = function(){
            if ( !done && (!this.readyState ||
                  this.readyState == "loaded" || this.readyState == "complete") ) {
               done = true;
               if (callback)
                  callback();

               // Handle memory leak in IE
               script.onload = script.onreadystatechange = null;
            }
         };
      }

      head.appendChild(script);

      // We handle everything using the script element injection
      return undefined;
   },
});
</script>
James Messinger
  • 3,223
  • 3
  • 25
  • 16
  • 1
    nice. Have you experienced any issues that I should be aware of since you posted this answer? ;) – Andrew Matthews Jan 06 '10 at 02:15
  • I don't think this method makes use of the global ajax events.. not sure. – sharat87 Jan 06 '10 at 10:16
  • 1
    maybe to shrikant's point, i tried this out and it wasn't working for the script i wanted. i dug around and noticed that the script i wanted to debug was getting pulled in with $.ajax() instead of $.getScript(). i just changed it to use $.getScript() everything was cool. – Randy L Dec 06 '10 at 19:31
  • Thank you very much for sharing this. It will help me a lot. – yogsototh Jan 10 '11 at 14:10
  • script somewhat works but does throw unexpected errors when using eval(). but thanks for posting, its pretty nifty workaround. – tim Jan 20 '11 at 20:09
  • Most of this code is from the crossdomain section of "jQuery.ajaxTransport( "script", function(s) {" in the jquery library, in case you want to see more. – Will Shaver Jun 27 '11 at 15:35
  • Important safety tip: IE6/7 have issues with the last comma in the above code; they'll throw an error: `Error: Expected identifier, string or number`. Just remove it and you'll be fine again :) – Alex Oct 18 '11 at 09:40
  • Thank you for posting this. This is exactly what the doctor ordered! – fool4jesus Dec 12 '11 at 15:42
  • Agree with the answer, but may add:
    script.type = "text/javascript"; //or relevant MIME
    – prodaea Oct 29 '12 at 13:56
  • Excellent post! This is exactly what I needed. – Agorreca Apr 03 '13 at 14:22
  • 3
    This code doesn't work with $.when. Anyone has any idea how to fix it? – Alex Sorokoletov Feb 07 '14 at 16:59
  • Another turnaround I just found is to have the whole html page locally so the script is always loaded as a resource, not always possible but works in my case! – Vadorequest Feb 24 '15 at 15:06
21

With JQuery 1.6(maybe 1.5) you could switch to not using getScript, but using jQuery.ajax(). Then set crossDomain:true and you'll get the same effect.

The error callback will not work. So you might as well not set it up like below.

However, I do setup a timer and clear it with the success. So say after 10 seconds if I don't hear anything I assume the file was bad.

        jQuery.ajax({
            crossDomain: true,
            dataType: "script",
            url: url,
            success: function(){
                _success(_slot)
            },
            error: function(){
                _fail(_slot);
            }
        })
Eric Twilegar
  • 558
  • 6
  • 7
  • 1
    Nice one. I set crossDomain to options.development (bool) so when developing I can debug, else I can ignore it. – Kriem Apr 17 '12 at 12:02
  • 2
    I tried this and this seems to work. I do get to see the JS source in the debugger, and I can set a breakpoint. But there is one issue: Every time I go back and forth, a new JS file is donwloaded with new ID # (script.js?_=2345678) etc. Any workaround to this? – Samik R Nov 11 '12 at 21:46
  • @SamikR Set the `cache: false` so it will not add that random number to the end of file name – Arash Milani Jan 13 '13 at 20:46
  • @ArashMilani Thanks. I have since solved it by using the last copy of the script and not loading it through AJAX. Just checking for something like: if(!window.MyScript){ AJAX }, else { // Call load function. } Works nicely. – Samik R Jan 14 '13 at 23:47
  • @SamikR yeah that should also work :-) just my 2 cents: IMHO it adds an indention of `if` block that I normally avoid if I find a way around it – Arash Milani Jan 14 '13 at 23:51
14

For those who would like to debug scripts and use them with $.when (James Messinger's answer doesn't work well with $.when) I suggest to use this code:

var loadScript = function (path) {
  var result = $.Deferred(),
  script = document.createElement("script");
  script.async = "async";
  script.type = "text/javascript";
  script.src = path;
  script.onload = script.onreadystatechange = function (_, isAbort) {
      if (!script.readyState || /loaded|complete/.test(script.readyState)) {
         if (isAbort)
             result.reject();
         else
            result.resolve();
    }
  };
  script.onerror = function () { result.reject(); };
  $("head")[0].appendChild(script);
  return result.promise();
};

All credits and glory go to Benjamin Dumke-von der Ehe and his article: jQuery script insertion and its consequences for debugging

This works well with $.when and the script is totally visible and debuggable. Thanks.

Alex Sorokoletov
  • 3,102
  • 2
  • 30
  • 52
  • Nice! Straight out of the [jquery source](https://github.com/jquery/jquery/blob/1.11.0/src/ajax/script.js) with the minor change that the script is not removed from the DOM. – styfle May 01 '14 at 03:21
  • 1
    Awesome. Also, it seems like $.getScript would prevent caching for me. Switching to your solution fixed that as well - which should speed things up quite a bit for our customers! – Jannik Jochem Nov 07 '14 at 10:23
2

There is an easy way to debug it with Chrome.

1- Write a console.log("something") on the line that you want to debug.

2- Watch the console for that log.

sample log

3- Click on the address link in front of the log.

4- Set break-point on that line.

DaNeSh
  • 1,022
  • 1
  • 14
  • 24
2

Try this,

jQuery.extend({
getScript: function(url, callback) {
    var head = document.getElementsByTagName("head")[0];

    var ext = url.replace(/.*\.(\w+)$/, "$1");

    if(ext == 'js'){
        var script = document.createElement("script");
        script.src = url;
        script.type = 'text/javascript';
    } else if(ext == 'css'){
        var script = document.createElement("link");
        script.href = url;
        script.type = 'text/css';
        script.rel = 'stylesheet';
    } else {
        console.log("Неизветсное расширение подгружаемого скрипта");
        return false;
    }



    // Handle Script loading
    {
        var done = false;

        // Attach handlers for all browsers
        script.onload = script.onreadystatechange = function(){
            if ( !done && (!this.readyState ||
            this.readyState == "loaded" || this.readyState == "complete") ) {
                done = true;
                if (callback)
                callback();

                // Handle memory leak in IE
                script.onload = script.onreadystatechange = null;
            }
        };
    }

    head.appendChild(script);

    // We handle everything using the script element injection
    return undefined;

} 
   });
Andro Selva
  • 53,910
  • 52
  • 193
  • 240
alexpts
  • 21
  • 1
1

All the answers are on this page somewhere but I wanted to summarise for future readers.

Checking the (dynamically added) File Downloads

Using Chrome, you can see Javascript files added using $.getScript (http://api.jquery.com/jQuery.getScript/) under the Network panel on the XHR tab; note they don't appear under the JS tab.

Debugging the File

1) Setting a break point in code. As mentioned in other answers\comments you can insert a

debugger;

statement in the Javascript code. This will invoke the browser's debugger. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger for more information.

2) Using a Source Map to make it appear in the browser's Sources panel (test in Chrome).Add

//# sourceURL=whatevername.js

to the end of your file. [The file appears under (no-domain) in Chrome source panel].

See: How to debug dynamically loaded JavaScript (with jQuery) in the browser's debugger itself? and https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map for more information.

3) Override $.getScript to always use an external reference as per the accepted answer (I haven't tested).

Anthony
  • 1,776
  • 17
  • 29
0

To avoid a lot of extra coding you can try this. In the file where you've declared your $('document').ready() (or any other file your debugger will access), add something like...

$.debug = function(name) {
   var n = name;
}

Add a breakpoint to the assignment line in your debugger. Then, in any other js-file you load with $.getScript() you can add ...

$.debug("some string to identify this point of code");

Whenever this line is executed your debugger will stop and wait for your command. Step out of the $.debug function and that's it!

user1904991
  • 140
  • 5
0

In Firefox 38.6.0 with Firebug 2.0.14 when I go to the Script tab, I see an entry in the drop down menu like jquery-1.11.1.js line 338 > eval and that contains the loaded script. Plus, looking at the code in this version of jQuery, it looks like internally $.getScript() is using $.get() and ultimately $.ajax(), the only difference being the eval() part for the script, which is handled by the jQuery globalEval() function:

// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
    if ( data && jQuery.trim( data ) ) {
        // We use execScript on Internet Explorer
        // We use an anonymous function so that context is window
        // rather than jQuery in Firefox
        ( window.execScript || function( data ) {
            window[ "eval" ].call( window, data );
        } )( data );
    }
},
AsGoodAsItGets
  • 2,886
  • 34
  • 49
0

This combines the OP solution with that of Eric. Override the necessary jquery to treat gets as always crossdomain and they'll show up perfectly without breaking a single thing in the jquery promise implementation.

jQuery.extend({
get: function (url, data, callback, type) {
 // shift arguments if data argument was omitted
 if (jQuery.isFunction(data)) {
    type = type || callback;
    callback = data;
    data = undefined;
 }

 return jQuery.ajax({
    url: url,
    type: "GET":,
    dataType: type,
    data: data,
    success: callback,
    crossDomain: true
 });
}


});
osoblanco
  • 468
  • 2
  • 10