Notes · Archive

evergreen

Making the browser download scripts in parallel

Loading multiple scripts concurrently instead of the browser’s serial default.

· 3 min read

javascript, performance, browsers, archive

Cite this
APA
Mangalapilly, Y. J. (2009, July). Making the browser download scripts in parallel. Saṃhitā Notes. https://yesudeep.com/blog/parallel-script-loading/
BibTeX
@online{mangalapilly2009making,
          author  = {Yesudeep Jose Mangalapilly},
          title   = {Making the browser download scripts in parallel},
          journal = {Sa\d{m}hit\=a Notes},
          year    = {2009},
          month   = {July},
          url     = {https://yesudeep.com/blog/parallel-script-loading/},
          urldate = {2009-07-29},
        }
Plain
Yesudeep Jose Mangalapilly. “Making the browser download scripts in parallel.” Saṃhitā Notes, 2009. https://yesudeep.com/blog/parallel-script-loading/.
RIS
TY  - ELEC
        AU  - Mangalapilly, Yesudeep Jose
        TI  - Making the browser download scripts in parallel
        T2  - Saṃhitā Notes
        PY  - 2009
        UR  - https://yesudeep.com/blog/parallel-script-loading/
        Y2  - 2009-07-29
        ER  - 

Note

Originally published on my old WordPress blog in 2009. Preserved here with its original date; the original is still online. Do not use this today — a 2026 note: the problem this post solves no longer exists. Browsers ship speculative preload scanners (IE8, Safari 4, and Firefox 3.5 had in fact already gone parallel when this was written — the claim below was true of the installed base, not the frontier), async and defer are universal, and document.write-injected scripts are now actively penalized by Chrome and refused trust by a strict strict-dynamic CSP, since they are parser-inserted. The technique aged from clever to obsolete to harmful; it's preserved as a record.

Current browsers download scripts serially.  What that means is that if you use:

<script type="text/javascript" src="foobar.js"></script>
<script type="text/javascript" src="quuxdoo.js"></script>

The browser will not fetch quuxdoo.js until it has finished downloading foobar.js.   In fact, it will not download any other resource until that script is downloaded.  This is really a sad state of affairs.

An existing “solution”

An article “Downloading JavaScript files in Parallel” by Rakesh Pai explains the issue with a couple screenshots displaying the Firebug Net panel.

<script type="text/javascript">
    (function() {
        var s = [
            "/javascripts/script1.js",
            "/javascripts/script2.js",
            "/javascripts/script3.js",
            "/javascripts/script4.js",
            "/javascripts/script5.js"
        ];

        var sc = "script", tp = "text/javascript", sa = "setAttribute";
        for(var i=0, l=s.length; i<l; ++i) {
            if(window.navigator.userAgent.indexOf("MSIE")!==-1 || window.navigator.userAgent.indexOf("WebKit")!==-1) {
                document.writeln("<" + sc + " type=\"" + tp + "\" src=\"" + s[i] + "\" defer></" + sc + ">");
            } else {
                var t=document.createElement(sc);
                t[sa]("src", s[i]);
                t[sa]("type", tp);
                document.getElementsByTagName("head")[0].appendChild(t);
            }
        }
    })();
</script>

He provides a solution that seems to work well, but contains a few things I didn't like or consider bugs:

  1. Browser sniffing.  Do Firefox and Opera not support the document.write() method? No, really, I'm asking here.  I don't have a copy of FF 1.5 or 2.0 lying around to verify.  The latest version of Opera does seem to indeed.  And not just that, the script checks for the browser per iteration of the loop, so if you have 10 scripts that you load with this method, the browser check will occur 10 times!
  2. The code tries to optimize for space but ends up being harder to read instead.  This should ideally be the job of a minifier like YUI Compressor, Douglas Crockford's JSMIN, Dojo Toolkit's ShrinkSafe, or Dean Edwards' Packer.  A couple of those space-saving tricks can be used to hint the minifier better while keeping the code readable.
  3. Take HTML 5.  New script element attributes like async and defer aren't handled.  Both are defined as boolean attributes and async=”true” means the script would execute asynchronously as soon as it is available. What about scripts that aren't of mime type text/javascript?  A better approach would have been to consume an array of attribute objects and fill those attributes into the created script element.
  4. Loop invariants should be outside the loop especially in a language like JavaScript where linear scope chains determine how quickly a variable can be accessed.  The code negligently accesses global objects and repeatedly goes down nested objects, which it really doesn't need to per iteration.
  5. XHTML isn't dead just yet.  Attribute minimization (ref. “<script … /defer/></script>”) is not valid XHTML.  Language purists may cringe.

Where's the code?

Here is a non-browser sniffing version that offers the above flexibility.  Please note, as of this time I do not have access to any version of the-browser-that-shall-not-be-named (blah, pottermania), so I'm relying on the community to test this piece of code in the-browser-that-shall-not-be-named.  Enough said.  Here's my attempt at the code:

/**
 * Loading scripts in parallel.
 * Copyright (C) 2009 Yesudeep Mangalapilly.
 *
 * Licensed under the terms of the MIT License.
 */
function getScripts(scripts){
    var lt = "%3C",
        gt = "%3E",
        doc = document,
        len = scripts.length,
        html = [];

    for (var i = 0, script = null, attribute_string = null; i < len; ++i){
        script = scripts[i];
        switch (typeof script){
        case 'string':
            attribute_string = ['src=\"', decodeURI(script), '\"'].join('');
            break;
        case 'object':
            var attrs = [];
            for (var key in script){
                var value = script[key];
                switch(key){
                    case 'src':
                    case 'SRC':
                        value = decodeURI(value);
                        break;
                    default: break;
                }
                attrs.push([key, '\"' + value + '\"'].join('='));
            }
            attribute_string = attrs.join(' ');
            break;
        default:
            continue;
        }
        html.push(lt, 'script ', attribute_string, gt, lt, '/script', gt);
    }
    html = unescape(html.join(''));
    doc.write(html);
}

Usage

Here is how you can use the code:

var scripts = [
    {
        src: "foobar.js",
        async: true
    },
    "quuxdoo.js"
];

getScripts(scripts);

Hope that helps.  Suggestions and constructive criticism most welcome.

How to cite

APA
Mangalapilly, Y. J. (2009, July). Making the browser download scripts in parallel. Saṃhitā Notes. https://yesudeep.com/blog/parallel-script-loading/
BibTeX
@online{mangalapilly2009making,
          author  = {Yesudeep Jose Mangalapilly},
          title   = {Making the browser download scripts in parallel},
          journal = {Sa\d{m}hit\=a Notes},
          year    = {2009},
          month   = {July},
          url     = {https://yesudeep.com/blog/parallel-script-loading/},
          urldate = {2009-07-29},
        }
Plain
Yesudeep Jose Mangalapilly. “Making the browser download scripts in parallel.” Saṃhitā Notes, 2009. https://yesudeep.com/blog/parallel-script-loading/.
RIS
TY  - ELEC
        AU  - Mangalapilly, Yesudeep Jose
        TI  - Making the browser download scripts in parallel
        T2  - Saṃhitā Notes
        PY  - 2009
        UR  - https://yesudeep.com/blog/parallel-script-loading/
        Y2  - 2009-07-29
        ER  - 

Annotations

Thank you — your note is held for review and will appear once approved.

Thank you — your note is published.

Please sign in below to leave a note.

Type to search · ↑↓ to move · ↵ to open · Esc to close