When one or more module packages are sent to the client, the script components are wrapped in a closure. They are not immediately executed when the browser parses the script response. Instead, the closure is passed to the ResourceLoader client. This allows it to control the order in which the closures execute independently from the order in which they arrive from the server, with regards to the dependency tree (which the client has in memory). See the Client section for more information about the loading procedure and a walkthrough of a example scenarios.

All scripts are minified before being put in the package. For this we use the JavaScriptMinifier library. In case of a cache-miss, the minification is done on-the-fly on the web server. See also the Caching section for more about the performance of packaging and the caching infrastructure around it.

Example of how sprites work. Using a custom repeat or positioning would "leak" other parts of the sprite. This is one of the reasons why we don't use sprites.

Visual comparison of what @embed does.

Comparison loading CSS and referenced images with and without Embedding enabled. 27.3% reduction in total size after compression, 97.2% reduction in number of HTTP requests.

In order to reduce the number of HTTP requests for images used in the interface, ResourceLoader makes use of Data URI embedding. When enabled, images will be automatically base64-encoded and embedded into the stylesheet. While it will make the stylesheet larger (due to base64 inflation), it improves performance by removing the overhead associated with requesting all those additional files. The actual server response (which contains the minified result of all concatenated stylesheets and all embedded images in a single request) uses gzip compression. This enables the response to function a bit like a "super sprite" (more on this later). Regardless of the expansion caused by base64 encoding, the gzipped ResourceLoader response is still smaller than the sum of the individual CSS and image binary files.

To enable embedding for an image, use the "@embed" annotation in a CSS comment over the relevant css declaration. For example:

Using this technique makes traditional sprites obsolete. While the motivation behind sprites is good (less HTTP requests, better compression) it does come with a few caveats:

Maintenance. If an image needs to be updated, one has to regenerate the sprite file, update the background positions in the CSS output, etc.

Produces overly complex CSS.

Imposes restrictions on image usage. Properties background-repeat, background-size or background-position may not be used due these leaking other images in the same sprite.

These caveats aren't the end of the world (sprites are in wide use, clearly they do work). Some other resource delivery systems do use sprites, some even perhaps some of the maintenance in an automated fashion. The automated embedding, however, provides the best of both worlds – without any of the caveats.

The advantages of sprites still hold up:

Reduced number of HTTP requests.

Improved compression by combining images in one file.

In addition to that:

No maintenance.

Clean CSS.

No restrictions or sprite "leakage" bugs.

Even smaller number of requests. The CSS and the images are now in the same request.

No download delay flash. Once the stylesheet is there, all the images are as well. This improves perceived performance. Browsers normally don't download files referenced in CSS until their selectors are active (e.g.. :hover). This would cause a flash when the user first hovers a button. Embedding makes the image instantly available from the data URI.

ResourceLoader applies path remapping for images not being embedded (e.g. images that exceed the size limit or for browsers that don't support embedding). When the stylesheets are combined during the packaging process, these image urls would break as they would lose the context of a parent directory. Remapping corrects these urls to be relative to the loader location instead.

With the Flipping functionality it is no longer necessary to manually maintain a copy of the stylesheet for right-to-left languages. ResourceLoader automatically changes direction-sensitive CSS declarations (and more). Internally, the CSSJanus library provides that smart "flipping" logic.

Aside from flipping direction-oriented values, it also converts property names and shorthand values. And it converts references to filenames ending in -ltr into filenames ending in -rtl, thereby loading direction-specific iconography,

Sometimes you may want to exclude a rule from being flipped. For that one can use the @noflip annotation. This instructs CSSJanus to skip the next CSS declaration. Or, when used in the selector part, it skips the entire following CSS ruleset.

As mentioned, all resources are combined in a single package. The loader response from the server bundles both scripts and styles from the requested module(s) in the same request. The Client receives this and loads the stylesheet in the DOM at the right time, so they are in memory when the relevant scripts that use these CSS classes, execute.

This means that neither the JS nor the CSS will run if JS is disabled. However, if you need the CSS to still run, you can add one or more CSS-only module with OutputPage->addModuleStyles( $modules ).

Messages are exported as a JSON blob, mapping the message keys to the correct translation. They're fetched on the server from MediaWiki's localization framework (including its language fallback logic). Only message keys used by the module are included in the package.

As with the other two resource types, the messages component is also optimized to load only what is necessary for the requesting environment. This is especially important considering that MediaWiki is localized in over 300 languages. Only 1 unique set of messages is delivered to the client.

The startup module is the first and only hardlinked script being loaded on every page from a <script> tag. It is a lightweight module that does 3 things:

Sanity checkIt starts by performing a quick sanity check that bails out if the current browser is not supported. This saves bandwidth as well as preventing broken interfaces, basically leaving the user with an untouched page with the natural non-javascript fallback behavior. Browsers such as Internet Explorer 5 and early versions of Mozilla fall in this category. For those the startup module is the first and last script to be loaded.

Module manifestIt exports the module manifest. Containing all the dependency information of all modules, cache group (if any) and the last-modified timestamp of each module.

ConfigurationA subset of the server-side configuration of MediaWiki is made available to client-side scripts.

For example, imagine two unrelated modules both making use of a third module that is exceptionally large (e.g. jQuery UI). One module is used on page "Foo", the other on "Bar". Without a module store, the following would happen:

This second request would not be a cache hit at first because it has a different url serving a different combination of scripts. Having the user re-download the big module again would be unfortunate. With the module store, each module gets its own cache entry, which is not affected by other modules in the same request. Let's look at the same scenario again:

Script resources: No longer minified, concatenated and loaded from load.php. Instead, load.php will instruct the client to request each source file directly. This makes debugging scripts easier with your browser's developer tools. Transformations such as closure don't apply, so scripts may execute in the global scope.

Style resources: No longer minified, concatenated and loaded from load.php. Instead, load.php will instruct the client to request each stylesheet directly. Transformations such as URI embedding and RTL-flipping don't apply.

When in debug mode (and only in debug mode) certain stylesheets (namely those loaded through a <link> tag) still get RTL-flipping applied (see talk page).

Query parameter debug (string): Set to "true" to enable, to "false" to disable. When absent, falls back to next step.

http://example.org/wiki/Main_Page?debug=true

Cookie resourceLoaderDebug (string): Set "resourceLoaderDebug=true; " to enable, delete the cookie to disable (setting to "false" does not work). When absent, falls back to next step. There is a user script available to simplify toggling this. Add:

to Special:MyPage/common.js on the wiki where you're debugging. A link to enable or disable debug mode will be added to the toolbox. In some browsers, you may need to hard refresh before the change takes effect.

$wgResourceLoaderDebug (boolean): The default mode is determined by this configuration setting. Unless overridden in LocalSettings, this will be set to false. A production wiki should never set this to true as debug mode will then be served to everybody. Thus being inefficient and likely introducing bugs due to the nature of debug mode.

Module groups are used to control cache fragmentation. By default all modules are allowed to be loaded in the same request. The client store prevents most cache fragmentation automatically, but in case fine-tuning is needed, a module group may be used. Use groups sparingly as they naturally require additional HTTP requests.

Any freeform string can be used as a group name. Only modules in the same group are loaded in the same request.

Try to keep group names short as they are sent to the client as part of the module registry. Typically a substring of a module name is used (e.g. "jquery-ui" or "ext.foo").

Beware of reserved names. In addition to the cache fragmentation as described above, these reserved groups are disqualified for client store optimisation and have additional behaviour.

user. Reserved for modules that vary by username (e.g. user scripts). These HTTP requests get an additional "user" query parameter. This parameter is available in the ResourceLoaderContext object passed to content methods (e.g. getScript, getStyles). Due to the extra parameter, they don't share cache with other users or logged-out users. The cache will be public. The stylesheets in this module group are loaded after all other modules (last cascading order), using the "ResourceLoaderDynamicStyles" marker as separation.

private. Reserved for modules that are not allowed to be loaded from the public load.php endpoint (e.g. for CSRF tokens). Modules in this group are automatically embedded by OutputPage in the HTML when loaded. They cannot be loaded on demand.

site. Reserved for stylesheets that are user-generated content, but are not user-specific (rather for the entire site). The stylesheets in this module group are loaded after all other modules (last cascading order), using the "ResourceLoaderDynamicStyles" marker as separation.

noscript. Reserved for stylesheets that are user-generated content, but are not user-specific (rather for the entire site). The stylesheets in this module group are loaded after all other modules (last cascading order), using the "ResourceLoaderDynamicStyles" marker as separation.

ResourceLoader features on-demand generation of the module packages. The on-demand generation is very important in MediaWiki because cache invalidation can come from many places. Here's a few examples:

ExtensionsCore and extensions generally only change when a wiki is upgraded. But especially on large sites such as Wikipedia, deployments happen many times a day (even updates to core).

Users[2]Wiki users granted certain user rights (administrators by default) have the ability to modify the "site" module (which is empty by default and will be loaded for everybody when non-empty). This is all without servers-side access, these scripts/styles are stored as wiki pages in the database.On top of that, each user also has its own module space that is only loaded for that user.

TranslatorsThe interface messages are shipped with MediaWiki core and are generally considered part of core (and naturally update when upgrading/deploying core). However wikis can customize their interface by using the MediaWiki message namespace to modify interface messages (or create new ones to use in their own modules).

Every module has an automatically generated Last-Modified timestamp. This timestamp is based on a number of factors. All together, used to ensure proper cache busting when needed while also avoiding unnecessary re-generation of the package when it isn't needed.

JavaScript files and CSS filesLast modified timestamps from the files on disk.

Files referenced in CSSExtracted from the stylesheets (e.g. background: url(foo.png);, see also Remapping). Last modified timestamps from the files on disk.

LocalizationLast time any of the messages has been updated in the localization storage.

DependenciesIt works recursively for any module that this module depends on.

Because of all these different origins and cache invalidation factors, it is not desirable to have to manually (or scheduled) perform a "build" of some kind that would generate a huge package with everything in it.

Not only would it waste resources re-building many modules that haven't changed,

it would also be impractical to have one big build with everything in it because there are over 350 modules on an average Wikipedia site. Many of these come from extensions and wiki-users and these modules are likely only needed/loaded on certain pages or in certain states of the interface. For more on that see client side loader.

And it would not make efficient use of browser cache, since it would have to invalidate the entire "build" if even the slightest change is made (the solution that ResourceLoader has for this is explained under Balance).

Whenever a module is requested from the server, the above is evaluated. If needed, the cache will be re-generated. All phases of the packaging process have been optimized to be able to run on-demand on the web server. No build scripts, no periodic cron tasks.

Although the re-generation of a module package should be relatively rare (since cache is very well controlled), when it does happen it has to perform well from a web server.

For that reason it doesn't use the famous JSMin.php library (based on Douglas Crockford's JSMin) because it is too slow to run on-demand during a request response. Although JSMin.php only takes about 1 second for jquery.js[3] (which is okay if you're on the command-line), when working on-demand in a web server response (with potentially dozens of large files needing to be minified) waiting this long is unacceptable. Especially if potentially thousands of requests could come in at the same time, all finding out that the cache isn't up to date (to avoid a cache stampede).

Instead ResourceLoader uses an implementation of Paul Copperman's JavaScriptMinifier, which is up to 4X faster than JSMin. In addition to the speed, time has told that JavaScriptMinifier interprets the JavaScript syntax more correctly and succeeds in situations where JSMin outputs invalid JavaScript. The output size of JavaScriptMinifier is slightly larger than JSMin (about 0.5%; based on a comparison by minifying jquery.js[3], for which the difference was 0.8KB). The reason this is not considered a loss is because it is put in the bigger picture. ResourceLoader doesn't aim to compress as small as can be no matter the cost. Instead it aims for balance, get large gains in a wide range of areas while also featuring instant cache invalidation, fast module generation, a transparent "build"-free environment for the developer, etc. The fact that it could be a little bit smaller then becomes an acceptable trade off.

JavaScriptMinifier is a standalone library.It is freely licensed, open-source and available from the MediaWiki repository:Download: Git

↑These user-module features are disabled by default, but can be enabled from the server configuration. See: $wgUseSiteCss, $wgUseSiteJs, $wgAllowUserCss, $wgAllowUserJs. Wikipedia, for example, has these enabled. Also, the Gadgets makes it possible for users to create opt-in modules and/or separate features into multiple modules (as opposed to the "site" module which is only 1 module and loaded for everyone)

↑ 3.03.1Based on a git distribution of jquery.js (160 KB) from July 2011. JSMin.php took 0.85s to minify that.