ResourceLoader/Developing with ResourceLoader

From MediaWiki.org
Jump to: navigation, search
shortcut: RL/DEV

This is a quick walkthrough about the key points you need to understand to develop in MediaWiki core or an extension with ResourceLoader.

Registering[edit | edit source]

See also Manual:$wgResourceModules

In order for all the ResourceLoader features to function properly it is required to register modules under a symbolic name. We do that by adding the module definition to $wgResourceModules (if you're writing an extension), or to add the array to ./resources/Resources.php (if you're working in core).

$wgResourceModules['ext.myExtension'] = array(
	// JavaScript and CSS styles. To combine multiple files, just list them as an array.
	'scripts' => array( 'js/ext.myExtension.core.js', 'js/ext.myExtension.foobar.js' ),
	'styles' => 'css/ext.myExtension.css',
 
	// When your module is loaded, these messages will be available through mw.msg().
	// E.g. in JavaScript you can access them with mw.message( 'myextension-hello-world' ).text()
	'messages' => array( 'myextension-hello-world', 'myextension-goodbye-world' ),
 
	// If your scripts need code from other modules, list their identifiers as dependencies
	// and ResourceLoader will make sure they're loaded before you.
	// You don't need to manually list 'mediawiki' or 'jquery', which are always loaded.
	'dependencies' => array( 'jquery.ui.datepicker' ),
 
	// You need to declare the base path of the file paths in 'scripts' and 'styles'
	'localBasePath' => __DIR__,
	// ... and the base from the browser as well. For extensions this is made easy,
	// you can use the 'remoteExtPath' property to declare it relative to where the wiki
	// has $wgExtensionAssetsPath configured:
	'remoteExtPath' => 'MyExtension'
);

Notice that we only list the dependency we directly need. Other modules such as jquery and jquery.ui will be automatically loaded to support jquery.ui.datepicker. Also notice that because (in this example) ext.myExtension is a module provided by and related to an extension, the module name begins with "ext.".

Tip: Pass single resources as a string. Pass multiple resources as an array of strings.

CSS[edit | edit source]

Media queries[edit | edit source]

You can use media queries to specify when a CSS file applies:

	'styles' => array(
		'always.css',
		'print.css'  => array( 'media' => 'print' ),
		'highResolution.css'  => array( 'media' => 'screen and (min-width: 982px)' ),
	),

In the above example, the always.css stylesheet will always apply, the print.css stylesheet applies on print (and in the "Printable version" mode), and the highResolution.css stylesheet applies when the viewport width is at least 982 pixels.

Annotations[edit | edit source]

@embed[edit | edit source]

See also ResourceLoader/Features#Embedding

The @embed annotation triggers the Data URI embedding feature of ResourceLoader. Small images (up to 24kb per image) referenced with this annotation will be automatically embedded by ResourceLoader.

.mw-foo-bar {
    /* @embed */
    background-image: url(images/icon-foo-bar.png);
    padding: 4px  0 3px 40px;
}

In MediaWiki 1.22 and newer instead of the @embed annotation, LESS mixins are used to embed background images, for example the .background-image mixin:

.mw-foo-bar {
    .background-image('images/icon-foo-bar.png');
    padding: 4px  0 3px 40px;
}

If the image can be embedded, this will be processed to the following CSS code:

.mw-foo-bar {
    background-image: embed(images/icon-foo-bar.png);
    background-image: url(images/icon-foo-bar.png)!ie;
    padding: 4px  0 3px 40px;
}

If the image cannot be embedded, this will in contrast be processed to this CSS code:

.mw-foo-bar {
    background-image: url(images/icon-foo-bar.png);
    padding: 4px  0 3px 40px;
}

For the LESS code, which does this processing, see resources/mediawiki.less/mediawiki.mixins.less inside the MediaWiki Core.

@noflip[edit | edit source]

See also ResourceLoader/Features#Flipping

To disable the flipping functionality for one CSS declaration or on an entire ruleset, use the @noflip annotation:

For example:

/* @noflip */ 
.mw-portlet-item { 
    line-height: 1.25em; 
    float: left; 
}
 
/* This one flips! */ 
.mw-portlet-item { 
    margin-left: 0.75em; 
    /* ... except this one: */ 
    /* @noflip */ 
    margin-top: 0.5em; 
    font-size: 0.75em; 
    white-space: nowrap; 
}

Module structure[edit | edit source]

Work in progress
Top module

Two modules for most extensions. A module to be loaded from the <head> containing styles and/or scripts that need to run as soon as possible. This module should generally be a small as possible and be used to:

  • Style output by PHP
  • Insert or modify elements that change the location of anything above the "the fold" (such as inserting a banner that pushes the page down, or a sidebar widgets that pushes another sidebar portlet down, or a content action tab that causes a change in the position of other tabs)
Bottom module (default)
  • Insert or modify elements that are not visible right away (e.g. binding autocompletion to a form element or inserting a small absolutely positioned widget)
  • Anything else

Debugging[edit | edit source]

Toggle debug mode[edit | edit source]

ResourceLoader supports complex client-side web applications in production and development environments. As these different environments have different needs, ResourceLoader offers two distinct modes: production mode and debug mode (also known as "development") mode.

Debug mode is designed to make development as easy as possible, prioritizing the ease of identifying and resolving problems in the software over performance. Production mode makes the opposite prioritization, emphasizing performance over ease of development.

It is important to test your code in both debug and production modes. In day-to-day development, most developers will find it beneficial to use debug mode most of the time, only validating their code's functionality in production mode before committing changes.

Debug mode can be enabled for all page views, or for an individual view (with ?debug=true in the URL); see ResourceLoader/Features#Debug mode for details on toggling it.

Server-side exceptions[edit | edit source]

Errors thrown from ResourceLoader during module packaging in load.php requests are caught and output in a javascript comment on top of that request response (e.g. an error in a module definition such as missing files). You can inspect them through a panel for network requests in the developer tools for your browser.

You can also get the error in a log file by setting up a log file ($wgDebugLogGroups) in $wgDebugLogGroups['resourceloader']. They aren't added to the main debug log ($wgDebugLogFile) since logging is disabled by default for requests to load.php (bug 47960).

Breaking cache[edit | edit source]

When making frequent changes to code and checking them in a browser, the caching mechanisms designed to improve the performance of web-browsing can quickly become inconvenient. When developing on a system which is not making use of a reverse proxy such as Squid or Varnish, you only need to force your browser to bypass its cache while refreshing. This can be achieved by pressing CTRL+F5 in Internet Explorer, or holding the shift key while clicking the browser's refresh button in most other browsers.

If you are developing behind a reverse proxy, you can either change the values of $wgResourceLoaderMaxage or use ?debug=true to bypass cache since bug 25573 is fixed.

Loading modules[edit | edit source]

Server-side[edit | edit source]

see also Manual:OutputPage.php

While building the page, add one or more modules to the page by calling the addModules method on the OutputPage object and passing it one or more module names (such as "mediawiki.foo", "jquery.bar" or "ext.myExtension.quux")

$outputPage->addModules( 'example.fooBar' );

OutputPage adds the given module names to the load queue of the page. The client side loader requests all of the components for this module (scripts, styles, messages, dependencies, etc.) and execute them correctly. If your module contains a styles that affect elements outputted by PHP as well as elements created dynamically, then you should split the module. One for styling/enhancing the output, and one for dynamic stuff. The first module should have the "position" property set to "top" (in the module definition in $wgResourceModules), so that ResourceLoader will load it before parsing the rest of the HTML, preventing a FOUC (Flash of unstyled content). The other module doesn't need a "position" property and will simply be loaded asynchronously by the client, not blocking further parsing of the page.

Both should be added to the load queue in the same way, without repeating or forcing any particular loading style. This information is already in the module registry and the client will handle it accordingly.

$outputPage->addModules( array( 'ext.myExtension.foo', 'ext.myExtension.bar' ) );

To get a reference to OutputPage object, you can use the BeforePageDisplay hook.

CSS[edit | edit source]

If you have CSS that should be loaded even when JavaScript is unavailable, you can add it to the page with OutputPage::addModuleStyles which will load it with a <link> tag.

This is in contrast with the preferred method OutputPage::addModules which will load modules as a complete package in a single request (scripts, styles, messages) dynamically (from a lightweight client in JavaScript). This is the preferred method because it is more efficient (single request for all resources), it supports dependency resolution and batching only what is not yet loaded (through access to the startup manifest and state of other modules), and is highly cacheable (through access to the startup manifest with all version numbers it can dynamically generate permanently cacheable urls).

Since dependency changes can be deployed independently from caching the page, static loading with addModuleStyles cannot use dependencies. And since you can't dynamically access the latest version of the startup manifest from static HTML without JavaScript execution, it cannot have versions in the urls either and are therefore cached much shorter.

In practice you should only use OutputPage::addModuleStyles for stylesheets that are required for basic presentation of server-generated content (PHP output and the core Skin). Separate this CSS from modules with JavaScript and styling of dynamically-generated content, and don't have JavaScript modules depend on it, otherwise ResourceLoader may load modules twice.

JavaScript[edit | edit source]

JavaScript files are, like CSS files, also evaluated in the order they are defined in the scripts array.

In the following example, one would use the entry 'scripts' => array( 'foo.js', 'init.js' ) when registering the module.

// foo.js
var Foo = {
    sayHello: function ( $element ) {
        $element.append( '<p>Hello Module!</p>' );
    }
};
window.Foo = Foo;
 
// init.js
$( function () {
    // This code must not be executed before the document is loaded. 
    Foo.sayHello( $( '#hello' ) );
});

The page loading this module would somewhere use $this->getOutput()->addHTML( '<div id="hello"></div>' ); to output the element.

Client-side (dynamically)[edit | edit source]

If you only need a module in a certain scenario of the user interface, you could instead create a small init module (that is loaded server side), and from there use JavaScript to kick-off the load of the rest of the module when necessary. Use the mw.loader object for this.

Usage:

mw.loader.using( 'mediawiki.util', function () {
    /* This callback is invoked as soon as the mw.util is available */
} );

If you just want to load the module, and don't need the callback, you can use mw.loader.load( 'some.module.name' ); instead.

Don't worry about multiple (separate) scripts both asking the loader for the same module. ResourceLoader internally ensures nothing will be loaded multiple times. However requests when a request has been made already will tag along the existing request. And if it has already finished, then the loader will immediately run the callback without delay.

Tip: You can combine the two approaches. For example, if you put up a jQuery UI dialog after an AJAX request, you can call mw.loader.load( 'jquery.ui.dialog' ) right after you fire off the request so the two load in parallel, then wrap the code that creates the dialog:

mw.loader.using( 'jquery.ui.dialog', function () {
    /* code that calls $something.dialog() */
} );

Tip: You can combine different asynchronous dependencies using jQuery.when so they can run in parallel (instead of nesting them).

// Good: These three processes run in parallel
$.when(
  $.getJSON( '//api.example.org/foo' ),
  mw.loader.using(['mediawiki.util', 'mediawiki.Title', 'jquery.cookie']),
  $.ready
).done(function (fooData) {
 // This runs when the ajax request is complete, the modules are loaded,
 // and the document is ready
 $('#example').attr('href', mw.util.getUrl(fooData.page));
});
 
// Bad: These are nested, one waiting before the other starts:
$.ready.promise().done(function () {
  mw.loader.using(['mediawiki.util', 'mediawiki.Title', 'jquery.cookie']).done(function () {
    $.getJSON( '//api.example.org/foo' ).done(function (fooData) {
      $('#example').attr('href', mw.util.getUrl(fooData.page));
    });
  });
});
 
// Bad: The same (using callbacks instead of promises)
$(function () {
  mw.loader.using(['mediawiki.util', 'mediawiki.Title', 'jquery.cookie'], function () {
    $.getJSON( '//api.example.org/foo', function (fooData) {
      $('#example').attr('href', mw.util.getUrl(fooData.page));
    });
  });
});

See also[edit | edit source]

<translate>

  • <tvar|ResourceModules>$wgResourceModules</>: Array of all extra defined modules that can later be loaded during the output</translate>

<translate>

    • <tvar|ResourceModuleSkinStyles>$wgResourceModuleSkinStyles</>: Array of additional skin-provided stylesheets to existing ResourceLoader modules</translate>

<translate>

  • <tvar|ResourceLoaderDebug>$wgResourceLoaderDebug</>: The default debug mode (on/off) for ResourceLoader requests.</translate>

<translate>

<translate>

  • [[<tvar|loader>Special:MyLanguage/ResourceLoader</>|ResourceLoader]] (especially [[<tvar|developing>Special:MyLanguage/ResourceLoader/Developing_with_ResourceLoader</>|Developing with ResourceLoader]] to learn how to use it in MediaWiki core or an Extension)</translate>

<translate>

  • [[<tvar|hook>Special:MyLanguage/Manual:Hooks/ResourceLoaderRegisterModules</>|Manual:Hooks/ResourceLoaderRegisterModules]]

</translate>

ResourceLoader
Documentation Features · Vocabulary · Migration guide (users) · Migration guide (developers) · Developing with ResourceLoader · Default modules · Mobile support
Project information Status updates · Version 1 Design Specification (tasks) · Version 2 Design Specification (tasks) · Requirements
Other JavaScript Deprecations