Optimize order of styles and scripts
authorOri Livneh <ori@wikimedia.org>
Mon, 16 Mar 2015 02:45:17 +0000 (19:45 -0700)
committerTimo Tijhof <krinklemail@gmail.com>
Tue, 17 Mar 2015 02:10:49 +0000 (03:10 +0100)
commite86e5f8460b922cadac231142a495f3259c67b43
treeef957f803df48364a2ecf8979625e72d7dc81056
parent5ae7b2159c9d849fbf20958a13f2f13678148e8d
Optimize order of styles and scripts

The current ordering of scripts and stylesheets in <head> causes all major
browsers to serialize and defer requests that could be performed in parallel.

The problem is that external stylesheets are loaded before inline scripts. As
Steven Souders explains, "all major browsers preserve the order of CSS and
JavaScript. The stylesheet has to be fully downloaded, parsed, and applied
before the inline script is executed. And the inline script must be executed
before the remaining resources can be downloaded. Therefore, resources that
follow a stylesheet and inline script are blocked from downloading."[1]

In other words: the browser could start loading body images, but it refuses to
do that until it has executed inline scripts in head. And it refuses to execute
those scripts until the external CSS is downloaded, parsed and applied. You can
see the effect of this in this image, showing the request waterfall for
[[en:Gothic Alphabet]]: [2]. Notice how no images were requested before the
browser had finished processing the three load.php requests at the top.

To fix this, we want to move the inline scripts above the external CSS. This is
a little bit tricky, because the inline scripts depend on mw.loader, which is
loaded via an external script. If we move the external script so that it too is
above the external stylesheet, we force the browser to serialize requests,
because the browser will not retrieve the external CSS until it has retrieved
and executed the external JS code. So what we want is to move the inline
scripts above the external stylesheet, but keep the external script (which the
inline scripts depend on) below the external stylesheet.

We can do this by wrapping the inline script code in a closure (which binds
'mw') and enqueuing the closure in a global array which will be processed by
the startup module at just the right time.

Net result: external CSS and JS is retrieved in parallel, retrieval of images
(and other external assets) is unblocked, but the order in which code is
evaluated remains the same.

[1]: <http://www.stevesouders.com/blog/2009/05/06/positioning-inline-scripts/>
[2]: <http://people.wikimedia.org/~ori/enwiki-waterfall.png> (excerpted from
 <http://www.webpagetest.org/result/150316_0C_7MB/1/details/>.

Change-Id: I98d383a6299ffbd10210431544a505338ca8643f
includes/EditPage.php
includes/OutputPage.php
includes/debug/MWDebug.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/skins/Skin.php
includes/specials/SpecialJavaScriptTest.php
tests/phpunit/includes/OutputPageTest.php