I am working on a large website with a lot of existing third-party products already set up on it.
I want to add a cookie consent script, which needs to be the first script executed.
So I have added it at the top of <head> and when I do View Source I can see it is there at the top.
<head>
<script src="https://path/to/cookie/consent/stuff/cookie_script.js">
...
And then further down various other external libraries and frameworks are loaded: jQuery, GoogleAds, Hotjar and so on
...
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" async="async" src="https://securepubads.g.doubleclick.net/tag/js/gpt.js"></script>
...
</head>
But by the time the page has finished loading, when I inspect the DOM using the Web Dev Tools, I can see that my cookie script is no longer the first element in the <head>. Some other elements have been inserted in front of it.
<head>
<script src="https://www.googletagservices.com/activeview/js/current/osd.js?cb=%2Fr20100101"></script>
<script type="text/javascript" async="" src="https://www.google-analytics.com/analytics.js"></script>
<script src="https://securepubads.g.doubleclick.net/pagead/js/rum.js"></script>
...
// Cookie consent script has been pushed down to here
<script src="https://path/to/cookie/consent/stuff/cookie_script.js">
...
</head>
What can I do about this?
These elements are inserted AFTER the page is loaded from your server, this means that by the time that these scripts are loaded and executed, your script have already run.
You can detect this programmatically using something like:
setTimeout(function() {
var topScript = document.querySelector("script");
if (topScript.getAttribute(src) != ...) {
//takeAction
}
}, yourTimeout);
For your action, you can use alert to warn the user to close/not save the page. Alternatively, you could try moving the script you want to be at the top back to the top
I have the following html code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/blazy/1.8.2/blazy.min.js" defer></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.9.0/js/lightbox.min.js" defer></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous" defer></script>
<!-- 26 dec flexslider js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer></script>
<script defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
</head>
<body>
</body>
</html>
I get an error, saying jQuery is not defined. Now even if I remove defer from my inline JS code, it says jQuery is undefined. For some reason I have to keep the jQuery plugins in the head and keep my JS code inline. My question is:
Why doesn't inline Javascript code get deferred when defer attribute is present on it?
Is there a way to imitate the defer behavior on my inline Javascript code? I can put that at the end of body tag if required.
The scripts with the defer attribute load in the order they are specified, but not before the document itself has been loaded. As defer has no effect on script tags unless they also have the src attribute, the first script that gets executed is your inline script. So at that time jQuery is not loaded yet.
You can solve this in at least two ways:
Put your inline script in a .js file and reference it with a src attribute (in addition to the defer attribute which you already had there), or
Let your inline script wait for the document and the deferred scripts to be loaded. The DOMContentLoaded event will fire when that has happened:
<script>
window.addEventListener('DOMContentLoaded', function() {
(function($) {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
})(jQuery);
});
</script>
NB: Notice that in the latter case $(document).ready(function() is not included any more, as that would wait for the same event (DOMContentLoaded). You could still include it like you had in your original code, but then jQuery would just execute the callback immediately, which makes no practical difference.
You can create a Base64 URL out of the script and put it into the src!
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer>
</script>
I built a quick test to see it in action.
You should see an alert with Hello world! last if defer is working:
<script defer>
alert('Why no defer?!?');
</script>
<!-- alert('Hello world!'); -->
<script src="data:text/javascript;base64,YWxlcnQoJ0hlbGxvIHdvcmxkIScpOw=="
defer></script>
<script>
alert('Buh-bye world!');
</script>
Doing it manually is a little laborious so if you have the luxury of compiling your HTML in some way (Handlebars, Angular, etc.) then that helps a lot.
I'm currently using:
<script src="data:text/javascript;base64,{{base64 "alert('Hello world!');"}}"
defer>
</script>
You can also use type="module":
<meta charset="utf-8">
<script type="module">
let t = document.getElementById('top');
console.log(t);
</script>
<h1 id="top">Top Questions</h1>
https://developer.mozilla.org/docs/Web/HTML/Element/script#attr-type
From MDN docs:
defer
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.
This is called an IIFE (Immediately Invoked Function Expression) which gets executed before DOM is available. So, in that case jQuery is undefined because it it not in the DOM.
defer loading with plain text Data URI - Chrome and FF
#noLib #vanillaJS
suggest not to use on Cross Browser PRODuction
until MS IE dies and MS Edge will adopt the Chromium open source ;)
the only way to defer script is external file or Data_URI (without using event DOMContentLoaded)
defer
spec script#attr-defer (MDN web docs): "This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this case it would have no effect.)"
Data_URI
spec Data_URI
with right type "text/javascript" there is no need to base64 at all... ;)
using plain text so you can use simple:
<script defer src="data:text/javascript,
//do something with b-lazy plugin, lightbox plugin and then with flexslider
lightbox.option({
resizeDuration: 200,
wrapAround: true
})
">
yes, it's little bit weird hack, but <script type="module"> are deferred by default, there is no other option to mix following in exact order:
module external files - deferred by default
module inline scripts - deferred by default
external files - optionally deferred
inline scripts - only with this hack - as I know (without libraries/frameworks)
Defer/async script tags are not good enough
There is a common knowledge that you should use <script src=".." async defer> (or set script.async = true before assigning src, when you do it from JS) and/or put your scripts at the very bottom of the page, so that as much as possible of the page gets loaded and rendered to the user, as fast as possible.
defer.js (note: I am the author of this script) is written in plain JavaScript, making lazy-loading other contents more fast and performant. You can defer any javascript files as well as inline script blocks efficiently.
If your page is just an HTML page enhanced with some JavaScript, then you're good with just <script async>. It takes time for browser to parse and execute those scripts, and each UI change may reflow your layout, make your load speed more slow, no one likes staring at a blank white page; users are impatient and will leave quickly.
In various cases, using async or defer does not deliver faster page speed than defer.js does.
I checked all the proposed solutions but all have their disadvantages. So I invented my own.
Put this inline script into your head tag or right after the start of body tag:
<script>var Defer = []; document.addEventListener('DOMContentLoaded', function() { while (Defer.length) Defer.shift().call(); }); </script>
This one liner will collect all the inline scripts you want to defer and run them respectively as soon as document is fully loaded. Now anytime you need to run an inline script deferred, just register it like:
<script>
alert('This alert will show immediately.');
Defer.push(function() {
alert('This alert will show only after document is loaded.');
// You can use anything which is not loaded yet, like jQuery
$(".selector").doSomeJqueryStuff();
});
// You can use it as many times as you like and in any place of your DOM.
Defer.push(function() {
// Any inline code you want to defer
});
</script>
This inline script will run only after document is loaded. That means you can run inline jQuery script having your jQuery stay at the end of your DOM.
You can use this data url as src attribute
data:application/javascript,eval(document.currentScript.textContent)
which takes this current script tag and evaluate its content as if it was inside an external file.
it also works with lazy attribute.
it uses document.currentScript which not supported by IE browsers.
<script defer src="https://cdn.jsdelivr.net/npm/vue"></script>
<script defer src="data:application/javascript,eval(document.currentScript.textContent)">
console.log('defered', typeof Vue); // function
</script>
<script>
console.log('not defered', typeof Vue); // undefined
</script>
There is a somewhat less obscure way to accomplish deferral that does not require callbacks, promises, or data urls ... although it does a little DOM manipulation in the background. The tiny library (109 bytes compressed/gziped) https://www.npmjs.com/package/deferscript let's you do this. The example below is based on the original post.
<script src="https://cdnjs.cloudflare.com/ajax/libs/flexslider/2.6.3/jquery.flexslider.min.js" defer>
</script>
<script src="./deferscript.js" defer>
(function($) {
$(document).ready(function() {
//do something with b-lazy plugin, lightbox plugin and then with flexslider
});
})(jQuery);
</script>
All you have to do is insert a src attribute with the value ./deferscript.js.
If the problem is that jQuery variable $ is not defined, maybe you can create a fake $ function that returns a ready function waiting for the DOMContentLoaded?
All my inline scripts has $(document).ready(..... and the problem is that $ is not defined as the header scripts are deferred.
So, just add a fake $ in an inline script in head:
<script type="text/javascript">
var $ = function(element) {
return {
ready: function(callback) {
// in case the document is already rendered
if (document.readyState!="loading") callback();
// modern browsers
else if (document.addEventListener)
document.addEventListener("DOMContentLoaded", callback);
// IE <= 8
else document.attachEvent("onreadystatechange", function(){
if (document.readyState=="complete") callback();
});
}
};
};
</script>
I am adding some third party code to my page. This is the code sample they supply which works:
<script type="text/javascript">
document.write(unescape("%3Cscript src='//munchkin.marketo.net/munchkin.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script>
Munchkin.init('123-ABC-456');
</script>
I thought I would remove some tags and turned it into this:
<script type="text/javascript">
document.write(unescape("%3Cscript src='//munchkin.marketo.net/munchkin.js' type='text/javascript'%3E%3C/script%3E"));
Munchkin.init('381-KZC-440');
</script>
This now doesn't work, stating that Munchkin is not defined. I did a look around and read about scope but I'm still confused as to why it breaks, the order has been kept the same. If I had to guess I would say that the JS file being called is allowed to load completely before the page continues onto the next script tag, whereas in my version it continues too quickly. If so, this could presumably be used for many advantages as it's essentially an "onloadcomplete" event?
The new script won't be loaded until the current script block terminates. Think about it like this: The first code will result in a DOM structure like:
<script type="text/javascript">
document.write(...;
</script>
<script src='//munchkin.marketo.net/munchkin.js'></script>
<script>
Munchkin.init('123-ABC-456');
</script>
As you can see, the script is inserted after the script that calls document.write. Therefore the script is loaded when the third script element is evaluated.
But if you have
<script type="text/javascript">
document.write(...);
Munchkin.init('123-ABC-456');
</script>
<script src='//munchkin.marketo.net/munchkin.js'></script>
then you are trying to access the object before the script was loaded.
I'm learning JavaScript for a project, but I am stuck at the very beginning. I boiled it down, to the function in my script not being defined, but as near as I can tell it is defined.
I have a script: "script.js" with the function display result.
function displayResult()
{
document.write("hello world");
}
in the header of index.html I have this line
<script type="text/javascript" href="script.js"></script>
I have this line later
<body onload="displayResult()">
I have no idea why my function will not call. I would appreciate the help.
<script type="text/javascript" href="script.js"></script>
Should be:
<script type="text/javascript" src="script.js"></script>
there is no href attribute to a script block, its included from an external source through the src attribute.
BTW, calling document.write after the document has finished loading will first clear the entire content of the document, then replace it with whatever you pass to the call (in this case, 'hello world', which is not a valid HTML or XML document).
By first line of Javascript I meant the JS code inside tags with no "src" attribute.
By right it will be evaluated top down.
Read more about it from my previous answer: Load and execution sequence of a web page?
Let me explain here:
<script type="text/javascript">
alert(jQuery); // alerts jQuery's namespace
</script>
<script type="text/javascript" src="jquery.js"></script>
The result is alerting undefined.
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
alert(jQuery); // alerts jQuery's namespace
</script>
The result is alerting a object/function where defined.
The scripts are executed in the order they are listed in the html. It does not matter if they are inline JavaScript (within <script> tags) or if loaded as external scripts (<script src="xx.js">).