Defering JavaScript Execution

I’ve been working on a project that handles document.write calls post-load and can therefore be used to defer loading of all script elements on a page without the need to rewrite or refactor any of the existing JavaScript. It’s been tested on all major browsers (IE6 being the oldest) and with a number of ad servers, creative and third-party widgets.

Basically, you change your script element’s type attribute to “text/deferscript” and change src to defersrc. Since no browser knows what to do with a script of type “deferscript” it will simply skip over those tags when processing the HTML.

BEFORE:


<script src="foo.js"></script> 
<script type="text/javascript">
/* Use foo.js functions */
</script> 

AFTER:


<script [B]defer[/B]src="foo.js"></script> 
<script type="text/[B]defer[/B]script">
/* Use foo.js functions */
</script> 

Then at the bottom of the page, add two real script tags, one to reference the library and the other to start loading all of the deferscripts on the page. The setTimeout allows the browser to render the HTML prior to executing all of the scripts.


<script src="/docright.js"></script> 
<script>setTimeout(docright.loadscripts, 250);</script> 

It works by finding all of the script elements that were defered. Then it asynchronously loads them one at a time while proxying calls to document.write, document.writeln. The proxy then parses the HTML, converts those to DOM elements and inserts them before the original script element. (And yes, it will emulate the way browsers load scirpt elements that written out. Yes, even when those scripts write out other scripts … Yup, those scripts can write out stuff too! )

The results of this can be dramatic. Depending on how many script elements you have on a page, you could improve your time-to-first-visual by as much as 80%. (To get maximum benefit, make sure you move all of your <link> elements into the head! Otherwise the browser will block rendering until they’re downloaded)

Code is currently hosted at Google: http://code.google.com/p/document-right/ There’s a perl script available too that will handle converting the HTML for you. Looking for testers and reports of broken scripts.

You misunderstand my point.

It’s the HTML itself (the one you were document.write()'ing and are now “creating” using innerHTML) cannot contain scripts or care must be taken to manage consistently across browsers.

Maintaining a unique id for each instance on a page and adding a script element immediately below it is the answer? So the old markup:


<head>
<script src=//www.mywidgets.com/mywidget.js /> 
</head> 
<body> 

<script> callMyWidget(); </script >
<script> callMyWidget(); </script >
<script> callMyWidget(); </script >

The old markup could be converted to defer the loading if it looked like this :


<head>
<script defersrc=//www.mywidgets.com/mywidget.js /> 
</head> 
<body> 

<script type="text/deferscript"> callMyWidget(); </script >
<script  type="text/deferscript"> callMyWidget(); </script >
<script type="text/deferscript"> callMyWidget(); </script >

and your “simplified” version:


<head>
<script src=//www.mywidgets.com/mywidget.js /> 
</head> 
<script> callMyWidget("mywidget-1"); </script >
<div id="mywidget-2"> </div> 
<script> callMyWidget("mywidget-2"); </script >
<div id="mywidget-3"> </div> 
<script> callMyWidget("mywidget-3"); </script >

Consider the case where the HTML for the script is maintained as a fragment in some kind of CMS system. Now that system has to remember how many each fragment is embedded
so it can generate unique id’s?

Muh simpler code changes than are required to “defer” the document.writes

As with all things, the path of least resistance will be taken.

Your path requires the widget developer to

accept an element id as a parameter and retrieve it

change calls to document.write to string concatenation

set the innerHTML property of the document element

and the page author to:

maintain a set of unique id’s

update every call to a given piece of javascript

maintain which id’s are in use and where

Repeat those steps for each and every call to any piece of javascript

So the widget now sets the innerHTML property to a giant blob of HTML instead of calling document.write and passing a giant blob of HTML as a parameter. How is that any better code?

The page author’s burden is enormous.

So what are the steps required to implement this?
The developer has to …

Do nothing

The page author has to …

Change the attributes of their script elements.

Add a new script element at the bottom.

Yes it does since you assign each an id.

That method allows for as many biillions of script elements as you want in the page, you just need to assign an id in place of each document.write

So in those instances you place the script immediately after the new tag you added with the id so that you know that the id exists when the code that references it runs.

Much simpler code changes than are required to “defer” the document.writes

The problem with that is that the document has finished loading.
Any document.write() or document.writeln() calls will replace the content of the entire document.

Maybe wait until DOMContentLoaded is fired or run that code at the bottom of the page (i.e. just before the </HTML> tag)?

probably the easiest way is to wait document to be loaded then append head section with script tags.

You can do this this way


document.body.onload = function()
{
  var loc = array();
   loc[0] = 'proba.js';
   loc[1] = 'wiseblog.js';
  for(var i = 0; i < loc.length; i++)
  {
    var el = document.createElement('script');
    el.setAttribute('type', 'text\\javascript');
    el.setAttribute('src', loc[i]);
    document.head.appendChild(el);
   }
}

If you want this effect, I think script above is better solution.

That will invalidate your HTML though, won’t it?
Or is defersrc an allowed attribute? (I’m pretty sure it isn’t).

@Trilli: I know you’re an “expert programmer” but re-read the first post. Overriding document.write is exactly what this does. Your “solution” doesn’t actually do anything with the output. If you think that’s trivial you’ve never tried it.

Hi,
@ServerHerder is right, I didn’t read this problem till the end, this happens to me all the time. But now I think I get it. :slight_smile:

What we need to do is:

  1. Overwrite document.write method
  2. Write to response solution from mr. felgall
  3. When page loaded execute my script

Example:


<html>
    <head>
        <script language="javascript">
            var __dcoW = new Array();
            document.writeOriginal = document.write;
            document.write = function(data)
            {
                var __id = 'defere_'+Math.random()+'_xxx_'+Math.random();
                var obj = {ID: __id, Data: data};
                __dcoW.push(obj);                
                document.writeOriginal('<span id="' + __id + '"></span>');
            }
            function dgi(el)
            {
                return document.getElementById(el);
            }
            function defere()
            {
                for(var i = 0; i < __dcoW.length; i++)
                {                    
                    var __obj = dgi(__dcoW[i].ID);
                    var __new = document.createElement('span');
                    __new.innerHTML =  __dcoW[i].Data;
                    __obj.parentNode.insertBefore(__new, __obj);
                    __obj.parentNode.removeChild(__obj);
                }
            }
        </script>
    </head>
    <body onload="defere()">
        <script language="javascript">document.write('senad');
        document.write('<script language="javascript">function proba() { alert("www.wiseblog.info"); }  proba();<' + '/script>');
        </script>
    </body>
</html>

this could be part of solution.

It can be done with my solution, just what we need is to override document.write method


var __dcoW = new Array();
document.write = function(data)
{
  __dcoW .push(data);
}
document.body.onload = function()
{
  var loc = new Array();
   loc[0] = 'proba.js';
   loc[1] = 'wiseblog.js';
  for(var i = 0; i < loc.length; i++)
  {
    var el = document.createElement('script');
    el.setAttribute('type', 'text\\javascript');
    el.setAttribute('src', loc[i]);
    document.head.appendChild(el);
   }
  /*now we can loop trough __dcoW var and append desired text somewhere */
}

So is it ok now

@Immerse: Yes, setting the defersrc attribute will probably invalidate the HTML. It’s not totally required that you do this, instead you could set type=“text/deferscript” on those scrpit elements; however, IE will download any resource referenced by a src attribute, regardless of whether or not the type is “valid”. That might not be best, but if valid HTML is important enough then it would work.

@TriLli: That “script” won’t work at all if those scripts use document.write(). Moreover, if wiseblog.js depends on proba.js to execute, there’s not guarantee that they will execute synchronously. Also, that script requires extensive modifications to each and every page. Extracting the requisite scripts, their dependencies and adding the new code.

@felgall: The solution provides no means by which the script knows which of those divs it should write the content to … Moreover, it doesn’t allow that HTML to contain additional script elements. Finally, and I will always come back to this, it requires coordination between the script and the page author. Most CMS systems deal with HTMl fragments – pages are not being composed “as a whole” rather are composites of many of these fragments. Requiring the page define these id’s properly is too taxing for authors.

This is meant to be provide two things: First, allows you to load scripts that may or may not use document.write and be notified of their completion; Second, it provides a mechanism to easily begin late-loading all of the scripts on a page without the need to sacrifice functionality or rewrite existing code

Unfortunately banners are often served from different domains and quite often the banner server/ software has no idea where in a page it will be placed (i…e they don’t know which element with ID to write to).

While I agree that it would be much better to assign IDs to banner servers, that would mean totally reorganising banner companies and making sure that both the banner company and the client both know what a DOM element is and how to access it by ID.

That’ll take a while yet.

So while it’s not perfect and I wish it wasn’t such a mess, I do understand the need for fugly solutions using document.write()

Simply add <span id=“xxx”></span> (or a div) where ever you would have placed a script. Give each a different id.

Next update the script to replace document.write(‘yyy’) with
document.getElementById(‘xxx’).innerHTML = ‘yyy’;

Put the script immediately before the </body> tag instead of where you put the span or div.

Done. Now you have no more document.write statements to cause a problem.

Again though, where do you put the output? If you’re writing Javascript that displays content (and lots do …) there’s no way to identify where this script is in the DOM tree. If you don’t want to use document.write() you still need to assume your script runs in line which is one of the reasons document.write is so maligned.

Your alternative then is to give additional steps to the HTML author: Put this script tag at the bottom, then below it put another script that calls this function and pass it the id of the element you want to append it to … HTML authors are not JavaScript developers nor should they have to be.

I am sure a lot of people will find the option to be able to defer the running of the garbage scripts they are forced to use a useful option.

Spread the word :slight_smile:

But it’s not just for scripts that use document.write. Sites have more scripts in the head of their page than they require to render it. But re-factoring all that code to achieve maximum performance is daunting and time-consuming.

This lets you eschew that effort and still receive the benefits of late-loading.

The main one being that they haven’t learnt how to write JavaSceript properly or learnt it back in the stone age when that was the only option and haven’t bothered to keep up to date.

If you are forced to use such garbage masquerading as JavaScript in your page then being able to defer is a useful option. I agree that in many instances you don’t get a choice about the code you get to use.

I have found it possible to rewrite some of the garbage scripts that advertisers have supplied to me so as to get rid of the garbage and get the script working properly using less code but then not everyone has the time or knowledge to be able to apply such fixes themselves.

I am sure a lot of people will find the option to be able to defer the running of the garbage scripts they are forced to use a useful option.

ps. I don’t think Google employ anyone who has any JavaScript knowledge whatever. Most of the JavaScript on their site doesn’t deserve the name JavaScript and all of Google’s sites function much better if you disable JavaScript for their sites completely.

Why are you still using the antiquated document.write statement at all instead of updating your code for 2005 by replacing those statements with either DOM calls or innerHTML.

Had a feeling there’d be a purist here.

Two reasons:

  1. It’s not practical.
    Many sites don’t have the luxury of writing all of the code being included on a site. Ad, analytic and widget providers all make extensive use of document.write for a number of reasons.
    To get similar functionality out of innerHTML some browsers require the scripts be scraped out of the HTML manually. This can only occur after the innerHTML has been set which breaks a number of scripts which document.write inline scripts and then use variables declared within them immediately.
    The recommendation to use DOM calls is also impractical. For example, translate YouTube’s embed code into DOM methods and you’ll get a nasty surprise: It doesn’t work in IE! (The HTML contains an embed tag within an Object tag. Since the object is missing a type or classid attribute, it’s the embed tag that IE actually renders! IE, however, will not let you append an embed within an object.)
    The fact of hte matter is HTML is easy. The DOM is hard. Given the nature of many of the questions raised in these forums, I don’t think you can argue that easy is best :slight_smile:
  2. Where do you put your content?
    Since simplicity is king, what’s simpler for a widget author than to tell page authors (i.e. users of his widget) “Put this script tag on your page and our widgets will magically appear?”
    Without the use of document.write, the main method of identifying your script becomes invalid:

    map= d.getElementsByTagName("SCRIPT"); me= map[map.length - 1]

    What are your alternatives? Iterate over every script tag and match against a source? What if you provide a method for embedding the content instead of simply executing it within the remote code? What if the author wants your widget on her page twice?

I cover this on the project page. The fact that major Internet stalwarts (e.g. Google; Yahoo; etc.) have not shunned it entirely is further proof that these issues exist and are not easy to solve.

For so many people too quickly dismiss the use of document.write as wrong but provide no real alternative is a disservice.

Internet users don’t care about these Holy Wars; HTML authors don’t have time to rewrite all of their code. We’re providing an alternative here. Having the luxury of complete control over a page’s code (and the time to rewrite the bits you do) would be nice. Unfortunately, from my experience, that’s the minority.