Preload script without execute

Problem

In order to improve the page performance I need to preload scripts that I will need to run on the bottom page.

I would like to take control of when the script is parsed, compiled and executed.

I must avoid the script tag, because it is blocker for common render engines (geeko, etc).

I can't load it using defer property, because I need to control when the script is executed. Also, async property is not a possibility.

sample:

<html><head>
//preload scripts ie: a.js  without use the script
</head><body> ..... all my nice html here
//execute here a.js
</body></html>

This allows me to maximize the render performance of my page, because the browser will start to donwload the scripts content, and it will render the page at the same time in parallel. Finally, I can add the script tag, so the browser will parse, compile and execute the code.

The only way that I could do that is using a hidden image tag. (This is a simplified version of Stoyan)

i.e.

 <html><head>
 <img src="a.js" style=display:none;>
</head><body> ..... all my nice html here
 <script src="a.js">  
</body></html>

Question

I didn't find any problem using this technique, but does anyone know a better way to do this? Is there any meta prefetch?

Additional information

I'm using requirejs, so I'm trying to preload the modules code, without executing it, because this code depends of DOM elements.

Answers:

Answer

You should have a look at the following links:

http://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/

http://tomdale.net/2012/01/amd-is-not-the-answer/

And at how ember.js is using a tool called minispade and preprocessing with ruby to make the process of loading, parsing and running javascript modules fast.

Answer

With similar technique you may preload scripts and stylesheets using img for Internet Explorer and object tag for every other browser.

var isMSIE = /*@[email protected]*/false;

var resources = ['a.js', 'b.js', 'c.css'];

for (var i=0; i<resources.length; i++){
  if (isMSIE){
    new Image().src = resources[i];
  } else {
    var o = document.createElement('object');
    o.data = resources[i];
    document.body.appendChild(o);
  }
}

There is a blog post describing such a technique and outlining caveats: Preload CSS/JavaScript without execution.

But why don't you want to just use dynamically added scripts just like suggested in other answer, this will probably lead to a cleaner solution with more control.

Answer

You can use the prefetch attribute of a link tag to preload any resource, javascript included. As of this writing (Aug 10, 2016) it isn't supported in Safari, but is pretty much everywhere else:

<link rel="prefetch" href="(url)">

More info on support here: http://caniuse.com/#search=prefetch

Note that IE 9,10 aren't listed in the caniuse matrix because Microsoft has discontinued support for them.

More info here and more options for preloading, like prerender and more

Answer

For each script you'd like to download without executing, make an object containing a name and a url, and put those objects into an array.

Looping through the array, use jQuery.ajax with dataType: "text" to download your scripts as text. In the done handler of the ajax call, store the text content of the file (which is passed in first argument) in the appropriate object, increment a counter, and call an "alldone" function when that counter is equal to the number of files you are downloading in this manner.

In the "alldone" function (or later) do the following: Loop through your array again, and for each entry, use document.createElement("script"), document.createTextNode(...), and (...scriptNode...).appendChild(...) to dynamically generate scripts having the intended source inline, rather than via "src" attribute. Finally, do document.head.appendChild(...scriptNode...), which is the point when that script is executed.

I have used this technique in a project where I needed to use frames, where several frames and/or the frameset need identical JavaScript files, in order to make sure each of those files is requested only once from the server.

Code (tested and working) follows

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<html>
    <head>
        <script id="scriptData">
            var scriptData = [
                { name: "foo"    , url: "path/to/foo"    },
                { name: "bar"    , url: "path/to/bar"    }
            ];
        </script>
        <script id="scriptLoader">
            var LOADER = {
                loadedCount: 0,
                toBeLoadedCount: 0,
                load_jQuery: function (){
                    var jqNode = document.createElement("script");
                    jqNode.setAttribute("src", "/path/to/jquery");
                    jqNode.setAttribute("onload", "LOADER.loadScripts();");
                    jqNode.setAttribute("id", "jquery");
                    document.head.appendChild(jqNode);
                },
                loadScripts: function (){
                    var scriptDataLookup = this.scriptDataLookup = {};
                    var scriptNodes = this.scriptNodes = {};
                    var scriptNodesArr = this.scriptNodesArr = [];
                    for (var j=0; j<scriptData.length; j++){
                        var theEntry = scriptData[j];
                        scriptDataLookup[theEntry.name] = theEntry;
                    }
                    //console.log(JSON.stringify(scriptDataLookup, null, 4));
                    for (var i=0; i<scriptData.length; i++){
                        var entry = scriptData[i];
                        var name = entry.name;
                        var theURL = entry.url;
                        this.toBeLoadedCount++;
                        var node = document.createElement("script");
                        node.setAttribute("id", name);
                        scriptNodes[name] = node;
                        scriptNodesArr.push(node);
                        jQuery.ajax({
                            method   : "GET",
                            url      : theURL,
                            dataType : "text"
                        }).done(this.makeHandler(name, node)).fail(this.makeFailHandler(name, node));
                    }
                },
                makeFailHandler: function(name, node){
                    var THIS = this;
                    return function(xhr, errorName, errorMessage){
                        console.log(name, "FAIL");
                        console.log(xhr);
                        console.log(errorName);
                        console.log(errorMessage);
                        debugger;
                    }
                },
                makeHandler: function(name, node){
                    var THIS = this;
                    return function (fileContents, status, xhr){
                        THIS.loadedCount++;
                        //console.log("loaded", name, "content length", fileContents.length, "status", status);
                        //console.log("loaded:", THIS.loadedCount, "/", THIS.toBeLoadedCount);
                        THIS.scriptDataLookup[name].fileContents = fileContents;
                        if (THIS.loadedCount >= THIS.toBeLoadedCount){
                            THIS.allScriptsLoaded();
                        }
                    }
                },
                allScriptsLoaded: function(){
                    for (var i=0; i<this.scriptNodesArr.length; i++){
                        var scriptNode = this.scriptNodesArr[i];
                        var name = scriptNode.id;
                        var data = this.scriptDataLookup[name];
                        var fileContents = data.fileContents;
                        var textNode = document.createTextNode(fileContents);
                        scriptNode.appendChild(textNode);
                        document.head.appendChild(scriptNode); // execution is here
                        //console.log(scriptNode);
                    }
                    // call code to make the frames here
                }
            };
        </script>
    </head>
    <frameset rows="200pixels,*" onload="LOADER.load_jQuery();">
        <frame src="about:blank"></frame>
        <frame src="about:blank"></frame>
    </frameset>
</html>

other question closely related to above approach other related question

Answer

Why not to try this?

var script = document.createElement('script');
script.src = 'http://path/to/your/script.js';
script.onload = function() {
  // do something here
}

document.head.appendChild(script);

you can use .onload event to control when it is loaded. One caveat is that .onload() doesn't work in IE and you can use this:

script.onreadystatechange = function() {
  if (/^loaded|complete$/i.test(this.readyState)) {
    // loaded
  };
}

Additionally adding scripts via DOM is non-blocking and i believe you can perfectly achieve your goals with this approach.

Answer

I've answered the same question there:

https://stackoverflow.com/a/46121439/1951947

just use the <link> tag to preload your script and then you can use it with the <script> tag

eg: <link href="/js/script-to-preload.js" rel="preload" as="script">

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.