17

I noticed in the <head> of my site (for work), there are a lot of <link rel="stylesheet" type="text/css" href="" /> and <script type="text/javascript" src=""> tags. There are even more JavaScript/CSS files that are only loaded for specific pages (we're using CodeIgniter, and the file paths are passed to the header view).

I was considering using a conditional/asynchronous loader (eg. yepnope.js, head.js, etc.), but I noticed a small problem with doing this.

In our views, there is inline JavaScript, some uses $(function(){}) some uses $(document).ready(function(){}), and some just has code (using jQuery) that's not in a ready block.

Without editing EVERY view file to wrap its code in a function and calling that when the JS files are loaded, is there a way to delay the inline code until the JavaScript is asynchronously loaded?

Rocket Hazmat
  • 204,503
  • 39
  • 283
  • 323
  • 3
    No. but you can make your job easier by using `window.$ = waitUntilCodeLoaded`. That way you only change half the inline code. As a matter of fact, inline code is bad, make it all external – Raynos Jan 20 '12 at 19:21
  • @Raynos: I was considering moving all the code to their own files, but we have a lot of views, that would take a while. I was hoping there was a quick, hacky workaround I could do. – Rocket Hazmat Jan 20 '12 at 19:24
  • I know I *should* move all the scripts to their own files, but is there a quick, hacky workaround I can use to get the inline JavaScript to run when I want it to? – Rocket Hazmat Jan 20 '12 at 20:49
  • 1
    Also of interest: https://hacks.mozilla.org/2017/09/building-the-dom-faster-speculative-parsing-async-defer-and-preload/ – Christophe Roussy Dec 11 '17 at 14:12

7 Answers7

28

You can actually lazyload inline javascript: 1- Change the type parameter in the inline script to: text/delayscript

FROM

    <!– Inline Script –>
<script type="text/javascript" language="javaScript">
             /* Code */
</script>

To

    <!– Inline Script –>
<script type="text/delayscript">
             /* Code */
</script>

Giving the script tag a custom Mime type text/delayscript forces the browser to ignore its content (Please note that leaving it out entirely will default to text/javascript).

2- Lazy load all inline scripts Once heads.js (Or an other framework you might be using) confirms that it lazy loaded all your external JS, you can then grab the content of all your custom script tags and inject them in the page:

<script>
head.ready(function() {
    var 
        _head = document.getElementsByTagName("head")[0],
        _script = document.createElement('script'),
        _scripts = document.getElementsByTagName("script"),
        _txt = "text/delayscript",
        _contents = []
    ;

    for(var i=0,l=_scripts.length;i<l;i++){
        var _type = _scripts[i].getAttribute("type");
            if(_type && _type.toLowerCase() ==_txt)
                _contents.push(_scripts[i].innerHTML)
    }


    _script.type = 'text/javascript';
    _script.innerHTML = _contents.join(" ");
    _head.appendChild(_script);

});

To be even more graceful, you can actually keep the inline scripts in their original hierarchy in the DOM tree instead of jamming all their content in one script, as I have suggested above, by replacing the marked inline script tag by a new one that has mime type text/javascript:

head.ready(function() {
var 
    _scripts = document.getElementsByTagName("script"),
    _doc = document,
    _txt = "text/delayscript"
;

for(var i=0,l=_scripts.length;i<l;i++){
    var _type = _scripts[i].getAttribute("type");
        if(_type && _type.toLowerCase() ==_txt)
            _scripts[i].parentNode.replaceChild((function(sB){
                var _s = _doc.createElement('script');
                _s.type = 'text/javascript';
                _s.innerHTML = sB.innerHTML;

                return _s;
            })(_scripts[i]), _scripts[i]);
}
});
Rocket Hazmat
  • 204,503
  • 39
  • 283
  • 323
YoussefTaghlabi
  • 426
  • 4
  • 4
  • Heh, that's pretty clever. So, tell the browser to ignore the script tags, and then load them back into the page when the external scripts are loaded. I like it. – Rocket Hazmat Apr 17 '12 at 19:10
  • This will work as long as you don't have document.write in your inline scripts. So using something like yepnope.js (which I highly suggest) ), head.js or require.js in addition to this method I mentioned above, you can lazy load any legacy javascript on the fly, hence gaining tremendous perceived loading time. – YoussefTaghlabi Apr 18 '12 at 01:19
  • I'm not using `document.write`, so that's good. The problem was, I wanted to lazy load the externals, but the in-line code depended on it. With this method, I can run the scripts at the right time. :-) – Rocket Hazmat Apr 18 '12 at 02:01
  • Old question (greate answer) I know but this may be useful. If jQuery has also been deferred from loading and you are getting timing errors with $ is undefined. Wrap the above answer in a setInterval (var _si = setInterval...) and only execute the for loop when jQuery is loaded. (if (typeof $ != "undefined") { window.clearInterval(_si); for(var i = 0, l = _scrip...) – Ian Brindley May 01 '13 at 12:02
5

You have to consider moving inline code "outside" and include it with

<script defer="defer" type="text/javascript" src="">
Salaros
  • 1,394
  • 1
  • 12
  • 33
  • 1
    What exactly does `defer="defer"` do? – Rocket Hazmat Jan 20 '12 at 19:34
  • @Rocket: Just providing the reference, you'll have to ask the answerer for clarification :) – Colin Brock Jan 20 '12 at 19:47
  • 4
    @Rocket "So, it makes the file asynchronous" - no that is – Salaros Jan 21 '12 at 10:01
3

HTML5 introduced a new async parameter for scripts having a defined src.

You can add it directly on any <script> element :

<script src='/js/script.js' async></script>

BUT : keep in mind that it will not work on inline scripts!

And if your some of your pages mix either external and inline scripts, if you load the external asynchronously, it means that the inline scripts will actually be executed before the asynchronous ones... Which can have unwanted effects.

Set this Boolean attribute to indicate that the browser should, if possible, execute the script asynchronously. It has no effect on inline scripts (i.e., scripts that don't have the src attribute).

For instance, if you have the following configuration :

<script src='/js/jquery.min.js' async></script>
<script>
    // --> jQuery won't be loaded when this script will be executed!
    // This will throw an error.
    $(function () {
        $('#element').doSomething();
    });
</script>
Flo Schild
  • 4,540
  • 2
  • 34
  • 51
2

First of all I would recommend you to analyse the loading of the scripts on the client side very carefully and not only for the first JavaScript loading, but on the loading of the same JavaScript files at the second time or its loading on another page. If the ETag are correctly set on the scripts by the web server or if you use other caching options of HTTP (see the caching tutorial for details) for the JavaScripts files than no loading of the files itself will take place and only the cache revalidation will be done. So it can be that the problem which you describes is not so important like it looks like.

If you do decide to load some scripts dynamically you can use jQuery.getScript and place all dependent code inside of the success callback. If you need to load one or two JavaScript files the way will work very good, but if you need to load a list of JavaScript files with more complex dependency the implementation could be not so easy. In the case you can use document.writeln inside of <head>. The usage of the method inside of <head> has almost no disadvantages (see here for details).

Community
  • 1
  • 1
Oleg
  • 217,934
  • 30
  • 386
  • 757
  • I know how to dynamically load JavaScript. My problem is that I'm dynamically loading a library which is used somewhere in the body. So the inline script fails because the library isn't there. I was wondering if there was a quick way to fix that without copying all the JavaScript into their own files. – Rocket Hazmat Jan 26 '12 at 14:50
  • @Rocket: Why you can't place the code which use the library (the inline script) inside of `success` handle of the `$.getScript` call which loads the library? – Oleg Jan 26 '12 at 14:55
  • I can, but there are a lot of inline script tags. It would take a long time to do that. I was wondering if I could somehow make the browser do that automagically for me. – Rocket Hazmat Jan 26 '12 at 15:04
  • @Rocket: You *have to make some changes* either in the master page or in the page which needs the library. From nothing come nothing. Which changes are you ready to do? – Oleg Jan 26 '12 at 15:09
  • I was just trying to do as little changes as possible. I guess the master page would be easier. – Rocket Hazmat Jan 26 '12 at 15:11
  • Which information can you test in the master pager to verify that it will be now in the page which need the JavaScript library? You can test the things and if it's needed you can call `document.writeln` with ` – Oleg Jan 26 '12 at 15:17
  • What are you asking me to do? – Rocket Hazmat Jan 26 '12 at 15:27
  • Which criteria you have to detect whether the page use some specific library of not? Could you implement the detection inside of your master page? – Oleg Jan 26 '12 at 15:30
  • The master page knows what libraries it needs to load, that's not the issue. The issue is delaying the inline code until those are done. I probably will just move all the inline code to their own files, but I was just looking for a workaround. – Rocket Hazmat Jan 26 '12 at 15:53
  • 1
    @Rocket: You can read [here](http://blogs.msdn.com/b/kristoffer/archive/2006/12/22/loading-javascript-files-in-parallel.aspx) that if you execute `document.writeln(""); document.writeln("");` then the 'library.js' and 'scriptUsedLibrary.js' will be loaded *parallel*, but executed in the correct order. So If you detect in the master page that you need the library you can loads it dynamically. The scripts downloaded in the way in the `` are executed before `` – Oleg Jan 26 '12 at 16:12
  • Can I be sure that the libraries will run before the scripts in the body do? – Rocket Hazmat Jan 26 '12 at 19:59
  • 1
    @Rocket: I can't give you *no exact reference*, so you should test this. In my experience it work always perfect. You can include in the script which you load an alert or some other long operation to be more sure. You can examine the loading time for example in "Network" tab of the IE Developer tools, in Google Chrome Developer tools, in Firefox and so on. The tools shows that the Scripts included with `document.writeln` will be loaded *parallel* to other, but if the next script need the previous one one has no time any problems because the correct order of execution. – Oleg Jan 26 '12 at 20:30
1

Depending on your setup, you might want to look into the service from www.cloudflare.com. Their current beta functionality called rocket loader, does exactly that, including the inline scripts.

It's a free service. try it out ;)

Plus you get a free caching proxy on top :)

Rocket Hazmat
  • 204,503
  • 39
  • 283
  • 323
Michael Sørensen
  • 365
  • 1
  • 2
  • 14
  • I actually use CloudFlare for my personal website :-) – Rocket Hazmat Apr 18 '12 at 13:27
  • @Rocket Does your use case allow you to give it a try? Because they have the proxy process the scripts serverside, it's transparent to the developer. Including inline scripts. Rocket Loader does have it's quirks though. For instance the (inline) edit mode of CMS concrete5 is broken when rocket loader is on. – Michael Sørensen Apr 19 '12 at 06:52
  • This is for work. I can mention it to my boss, since I don't have control over the server/DNS. – Rocket Hazmat Apr 19 '12 at 13:16
  • I agree with @Michael Sørensen on rocket loader not working. As far as I know, from persnally using it, it simply loads your scripts in using document.write, and that method is very buggy. We were using cloudflare on our site and it broke the site. We could not figure out why after several hours of anger and thinking the entire site is fundamentally flawed, "oh it's just a faulty setting in cloudflare". Extremely annoying. – kiko carisse Sep 21 '17 at 13:53
1

Instead of inline scripts, create inline functions. Then at the end of your javascript file, call the function if it exists.

function inline_script() {.. code ..}

Inside your concatenated jquery+etc async javascript file:

if (typeof(inline_script) == 'function') inline_script()

You'll have to do some shuffling to make sure you only have one inline_script per page, or use some kind of caching to put them together if you call it more than once.

It's still a fun problem in the current age even though this question is old, async scripts = pretty awesome

Kevin
  • 4,039
  • 2
  • 32
  • 40
0

Have a look at using headjs. It is a nice lightweight library that will do this all for you.

skarE
  • 5,862
  • 2
  • 20
  • 23
  • I've looked at headjs, but the issue is all the inline JavaScript. I want to figure out a way to delay execution of script tags in the body until libraries are loaded. – Rocket Hazmat Jan 30 '12 at 15:20
  • You could wrap the contents of your script tags in comments and then regex match the code inside the comments then eval it in global scope :) – skarE Jan 30 '12 at 22:05