ResourceLoader/Migration guide for extension developers
Contents |
Most code written prior to MediaWiki 1.17, the introduction of ResourceLoader, should continue to work, there are some issues that are specific to the architecture of the system which may need to be resolved.
[edit] Crash-course: How to use ResourceLoader
Review resources/Resources.php for more examples of module registration and includes/OutputPage.php for more examples of how to add modules to a page.
If you are porting your extension to ResourceLoader, please add it to the list of ResourceLoader compatible extensions. The extensions on this list should also be useful as examples.
For now let's start with how to register a module:
[edit] Registering a module
This section is a work in progress, updating since r77011
To make your resources available to the loader, you will need to register them as a module, telling ResourceLoader what you want to make available and where the files are.
/* MyExtension.php */ $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() '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' ), // ResourceLoader needs to know where your files are; specify your // subdir relative to "/extensions" (or $wgExtensionAssetsPath) 'localBasePath' => dirname( __FILE__ ), '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 this 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.
[edit] Adding a module to the page
While building the page, if you want a module to be loaded before document ready, you need tell the OutputPage object to add one or more modules to the page.
$wgOut->addModules( 'ext.myExtension' );
Tip: Provide addModules with an array of module names if you want to load more than one.
OutoutPage::addModules adds the given the module name(s) to the load queue of the page. The client side loader will request all of the components for this module (scripts, styles, messages, dependencies, etc.) and load them. If your module contains a stylesheet that styles elements that are outputted by PHP as well as styles and/or scripts that insert content dynamically, then you should split the module into two separate modules. One for styling/enhancing the output, and one for dynamic stuff. The former module should have the "position" property set to "top", 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. Usage of OutputPage::addModuleStyles should be avoided.
$outputPage->addModules( 'ext.myExtension.foo', 'ext.myExtension.bar' );
[edit] Loading a module dynamically
If the page is loaded, it's still not too late to get your module loaded on the client. You will just need to ask the mw.loader object to get it for you.
mw.loader.using( 'ext.myExtension', function() { /* Do something with ext.myExtension, now that it's ready to use */ } );
Tip: If you just want to load the module, and don't need to run any code when it arrives, you can just use mw.loader.load( 'ext.myExtension' ).
[edit] Internationalization
You can access messages specified in your resource module using the mw.msg() method. Example:
alert( mw.msg( 'myextension-hello-world' ) );
mw.msg() can accept multiple arguments, with additional arguments being passed to the message function named in the first argument (exactly in the same way as with server-side message functions):
alert( mw.msg( 'myextension-about-me', myName, myTitle ) );
[edit] Inline JavaScript
In previous versions of MediaWiki, nearly all JavaScript resources were added in the head of the document. With the introduction of ResourceLoader, JavaScript resources are now (by default) loaded at the bottom of the body.
The motivation for this change has to do with the fact that browsers block rendering while a script resource is downloaded and executed (this is because a script can do almost anything to the page). By placing scripts at the bottom, the entire page can be loaded before any scripts are executed, ensuring that all stylesheets and images referenced by the HTML content as well as the CSS can be queued before the browser pauses to execute scripts, thus increasing parallelism, and improving client-side performance. This also ensures readers get earlier access to the articles and start reading.
A side-effect of this change is that scripts that have been injected arbitrarily into the different parts of the body can not depend on functionality provided by regular scripts (which are loaded at the bottom). It may be possible to improve backwards compatibility in the future, but there are no specific plans to do so at this time. We recommend to do javascript bindings from the modules rather than inline.
As of MediaWiki 1.18 there are multiple load queues in ResourceLoader (symbolically named "top" and "bottom"). Modules can set their "position" property (in $wgResourceModules) to one of these to force loading in either queue. A good use of a "top" module is styling HTML content generated by PHP. The mediawiki and jquery module are loaded from the top by default and can always be used.
[edit] Troubleshooting and tips
As the scripts are loaded at the bottom of HTML, make sure that all OutputPage-generated HTML tags are properly closed. If you are porting JavaScript code to ResourceLoader, make the default outer scope variable declarations explicitly use window as their parent, because the default scope in ResourceLoader is not global (e.g. not a window). Which means that code that previously became global by using var something in a .js file, is now local. It will only be global if you make it global, by literally attaching it to the window object with window.myProperty.
An even better approach (instead of attaching vars to window) is to convert the existing extension's JavaScript code to an object oriented structure. (or, if the code is mainly about manipulating DOM elements, make a jQuery plugin).
Suppose your current structure looks like this:
var varname = ..; function fn(a,b){ }
In order to keep it working with existing implied globals, replace implied globals with real globals (which it should already be):
window.varname = ..; window.fn = function( a, b ) { }
To actually port it to an object oriented structure, you'd write something like this:
mw.myExtension = { varname: ..., fn: function( a, b ) { } }
If you are using an external JavaScript library, which is primarily maintained outside of MediaWiki extension repository, patching as suggested above is undesirable. Instead, you should create an extra JavaScript file where the required variables and functions would be bound via the references to window. Then, when registering ResourceLoaderFileModule you should pass both scripts in one PHP array:
'scripts' => array( 'ext.myext.externalCode.js', 'ext.myext.externalCode.binding.js' )
This will weld the two scripts together before throwing them into a closure.
ext.myext.externalCode.binding.js should contain assignment statements like this:
window.libvar = libvar; window.libfunc = libfunc;
[edit] Example with full description
For a quick example of howto use Resource Loader to integrate the rare documentated jQuery features of MediaWiki, see Extension:FileSystemListing#Using_jQuery_to_build_collapsable_directory_tree_with_icons
[edit] See also
- $wgResourceModules: Array of all extra defined modules that can later be loaded during the output
- $wgExtensionAssetsPath: Path to extension assets - must be set for extensions to work in debug mode.
- $wgResourceLoaderMaxage:
- $wgResourceLoaderInlinePrivateModules:
- $wgResourceLoaderDebug:
- $wgResourceLoaderUseESI:
- ResourceLoader (especially Migration guide for developers to learn how to use it with Extensions)
- Manual:Hooks/ResourceLoaderRegisterModules
| ResourceLoader | |
|---|---|
| Documentation | Migration guide (users) · Migration guide (developers) · Developing with ResourceLoader · Default modules · Vocabulary |
| Project information | Status updates · Version 1 Design Specification (tasks) · Version 2 Design Specification (tasks) · Requirements |
| Other | JavaScript Deprecations |