How to load third-party javascript tag asynchronou

2019-02-12 10:52发布

问题:

We give out a piece of javascript tags such as <script src="http://ours.com/some.js"></script> which site owners put on their site like http://example.com and in this javascript tag we want to dynamically include a third-party js such as which can have document.write in it, but of course if we try to include it by conventional method,

var script_tag = document.createElement('script');
script_tag.type = 'text/javascript';
script_tag.src="http://third-party.com/some.js";
document.getElementById('target').appendChild(script_tag);

we get a warning from browser,

Warning: A call to document.write() from an asynchronously-loaded external script was ignored.

How do we get around this? Keep in mind, we don't really have control over third-party scripts so we can't change the logic in it. We are looking for some solution which can work across all browsers.

回答1:

The problem with loading a script on a already loaded document (instead of having the browser ignore the document.write()) is that you would delete all existent HTML. See this example so you can understand exactly what's happening, or for more details look at a documentation page for the document.write() method.

While I know this might not be what you're expecting to get as an answer, I believe you are out of luck since rewriting the script is not an option.

This appears to be a similar question with similar replies.



回答2:

You can support script injection the correct way by intercepting calls to document.write in this way:

document.writeText = document.write;

document.write = function(parameter) {
    if (!parameter) return; 
    var scriptPattern = /<script.*?src=['|"](.*?)['|"]/;
    if (scriptPattern.test(parameter)) {
        var srcAttribute = scriptPattern.exec(parameter)[1];
        var script = document.createElement('script');
        script.src = srcAttribute;
        document.head.appendChild(script); 
    }
    else {
        document.writeText(parameter);
    }   
};

Obviously this can be condensed down a bit further, but the variable names are included for clarity.

Source



回答3:

How about instead of loading the script by appending a script element, you load the contents of the script URL with an AJAX call and then use eval() to run it in the global scope? Here's an example and I did test it to verify that it works:

<!DOCTYPE html>
<html>
<head>
<script>

var xmlhttp;

if (window.XMLHttpRequest) {
  xmlhttp = new XMLHttpRequest();
}else{
  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

xmlhttp.onreadystatechange = function() {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    window.eval(xmlhttp.responseText); //Indirect call to eval to execute in global scope (http://perfectionkills.com/global-eval-what-are-the-options/)
  }
}

xmlhttp.open("GET", "https://third-party.com/test.js", false); //This is synchronous so that any document.write calls don't overwrite the entire page when they get called after the document is finished rendering. For all intents and purposes, this just loads the script like any other script, synchronously.

xmlhttp.send();

</script>
</head>
<body>

<div><h2>Hello World</h2></div>

</body>
</html>

And here are the contents I had in the test.js file:

document.write("This is a test...");
alert("...This is a test alert...");
console.log("...And a console message.");

I made the AJAX request for the script synchronous so that it would be loaded exactly as if it were a regular embedded script tag. If you run it asynchronously, and the script uses document.write after the page has been fully rendered, it clears the DOM and then writes to it... Kind of annoying actually. Lemme know if this works for you. :)



回答4:

Document.write will not work from async script because document is already loaded when script starts working.

But you can do this:

document.body.innerHTML = document.body.innerHTML + '<h1>Some HTML</h1>';



回答5:

Another procedure is to change the behavior of document.write() function.
Assume you have the main index.php file:

<html>
<head>
<meta charset="utf-8" />
</head>
<body>
Hello<br>
<div id="target"></div>
<script>
document.write = function(input) {
    document.body.innerHTML += input;
}
var doit = function() {
    var script_tag = document.createElement('script');
    script_tag.type = 'text/javascript';
    script_tag.src="http://127.0.0.1:8080/testPlace/jsfile.js";
    document.getElementById('target').appendChild(script_tag);
}
</script>
</body>
</html>

and the jsfile.js is like this:

document.write("OK MAN!");

now if you type doit() in the js browser console to execute that function (and the script do what you wrote) then the result would be:

Hello
OK MAN!

In which the html is like this:

<html><head>
<meta charset="utf-8">
</head>
<body>
Hello<br>
<div id="target"><script src="http://127.0.0.1:8080/testPlace/jsfile.js" type="text/javascript"></script></div>
<script>
    //That Script which here I removed it to take less space in answer
</script>

OK MAN!</body>
</html>


回答6:

What is the 3rd party javascript file?

If it's Google Maps JavaScript API v3 then make sure you include "&callback=your_init_funct" in the script URL. Then it will call 'your_init_funct' once the maps library is loaded so that you can begin displaying the map.

Another solution would be bezen.domwrite.js which is available here: http://bezen.org/javascript/index.html

Demo: http://bezen.org/javascript/test/test-domwrite.html



回答7:

Yes, document.write can't be called from an asynchronously loaded script, because it's detached from the document, so it can't write to it.

You can see the approach used here for the google maps api to get around this problem. So, it is possible some of your 3rd party scripts that you haven't named, could have the similar callback pattern implemented.

https://developers.google.com/maps/documentation/javascript/examples/map-simple?hl=EN

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Map</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <style>
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #map {
        height: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>

var map;
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng: 150.644},
    zoom: 8
  });
}

    </script>
    <script src="https://maps.googleapis.com/maps/api/js?callback=initMap"
        async defer></script>
  </body>
</html>