Recently, I moved my domain DNS record under Cloudflare. Cloudflare can smartly optimize web pages. But when I enabled HTML Auto Minify and Rocket Loader simultaneously, I found that the DOMContentLoaded
event was missing when accessing the web pages. In this post, I’ll share my experience and solutions to this problem.
Introduction to Auto Minify and Rocket Loader
Auto Minify can delete unnecessary characters (such as spaces, comments, etc.) in the website source code to reduce the source file size, including CSS, Javascript, and HTML. As a result, it reduces the amount of data that needs to be transmitted to the visitor and shortens the page loading time.
Rocket Loader reduces rendering time by asynchronously loading JavaScript, including embedded JavaScript in web pages and third-party scripts. Please refer to their blog for further details.
Finding that the DOMContentLoaded event is missing
As we all know, document.readyState
is defined as three states in Chrome:
- When the value is
loading
, it means that the browser is rendering the web page. - When it becomes
interactive
, DOM elements on the web page can be accessed. However, resources such as images, style sheets, and frames are still being loaded. - When finally it becomes
complete
, it means that all resources of the webpage have been loaded.
The DOMContentLoaded
window event is triggered when the state changes from loading
to interactive
. The load
window event is triggered when the state changes from interactive
to complete
.
However, after enabling HTML Auto Minify and Rocket Loader simultaneously, I found that the functions meant to be executed when the DOMContentLoaded
window event was triggered were not executed actually.
First of all, I judged that it’s impossible to be caused by bugs from web pages, as the website is working normally when I test them locally, and I also used the following code to ensure that the functions are directly executed when document.readyState
is interactive
or complete
:
if (document.readyState === "interactive" ||
document.readyState === "complete") {
foo();
} else {
window.addEventListener("DOMContentLoaded", foo);
}
So it’s puzzling.
Then I embed the following code into the web page so that starting from the time when JavaScript is executed, the console will show the value of document.readyState
every time the state changes:
console.log(document.readyState);
document.onreadystatechange = function () {
console.log(document.readyState)
}
Then learned from the result that after enabling both HTML Auto Minify and Rocket Loader, document.readyState
only has two states, loading
and complete
. The state interactive
is missing, and when the state changes from loading
to complete
, only the load
window event will be triggered. The DOMContentLoaded
event has never been triggered.
It seems to be a bug to Rocket Loader or may be just intentional.
According to the principles of Rocket Loader introduced by Cloudflare, it will postpone the loading of all JavaScript until rendering is finished. When rendering completes and the JavaScript code is being executed, document.readyState
should already be interactive
.
However, if HTML Auto Minify is also turned on simultaneously, document.readyState
is incorrectly set to loading
concluded from the result fore-mentioned. (I guess maybe a piece of code in Rocket Loader incorrectly assigned document.readyState
to loading
when it starts to execute Javascript code. Because Rocket Loader is not open-sourced, the reasons for doing this are unknown. Of course, the fact may be completely different from my guess.) As a result, the situation makes judgment for whether to directly execute functions completely invalid, resulting in the DOMContentLoaded
event still being registered.
Solution
Now that the mechanism is known to us, the solution is also straightforward. Add the following code before all DOMContentLoaded
event listeners. You don’t need to change any of the original codes and then, congratulations, you fixed this.
var inCloudFlare = true;
window.addEventListener("DOMContentLoaded", function () {
inCloudFlare = false;
});
if (document.readyState === "loading") {
window.addEventListener("load", function () {
if (inCloudFlare) window.dispatchEvent(new Event("DOMContentLoaded"));
});
}
This code judges that if the DOMContentLoaded
event has still not occurred after the load
event occurs, the code will manually triggers the DOMContentLoaded
event, thereby making the DOMContentLoaded
event equivalent to the load
event. The only disadvantage of this solution is that the DOMContentLoaded
event is only triggered when document.readyState
is complete
, but this is currently a necessary cost for fixing.
Of course, you can also resolve this problem by directly disabling the global Auto Minify for HTML, or Configure Page Rules for HTML pages that use the DOMContentLoaded
event so that the Auto Minify can be disabled on those pages.
Top comments (2)
Thanks for elaborating this post. I has helped me spot a similar issue with a client's site
Thanks for this! It was super useful! I find the solution to be unnecessarily intricate, however.
Not sure if this would work in your case, but the code block below worked fine for me