ResourceLoader/Package files

A package module is a type of ResourceLoader module that consists of multiple script or data files, specified using the  property, which can be accessed separately using. Regular modules can have multiple script files in the  property, but these will be concatenated and will not be separately accessible.

Why
Originally, this feature was conceived as a way to make code written for a NodeJS environment easier to integrate with ResourceLoader: libraries split across multiple files that  each other could be wrapped in a ResourceLoader module without modification (and perhaps with a MediaWiki-specific wrapper file). It has also proved useful for rewriting JS code in MediaWiki and extensions to put each class in its own file without needing to attach all classes to a global object.

Later, there was a proposal to add a feature to embed config variables in ResourceLoader modules, which ended up being implemented as dynamic files in package modules instead. Embedding config variables (and other data) in RL modules is often preferred over exporting them with or through the   hook, because that way they are not delivered to the client when the module that needs them isn't loaded, and benefit from longer caching times.

Embedding config and data in modules was previously possible, but required writing your own subclass of ResourceLoaderFileModule, an obscure technique that most developers don't know about or don't feel comfortable using. Most instances of this technique in MediaWiki core have now been ported to use package modules instead.

How it works
A package file can either be JavaScript code (script), or a JSON blob (data). A file's type is inferred based on its extension ( or  ). Files can be real files or the file system, or dynamic files generated by code. Dynamic files are commonly used to export the values of configuration settings or other data from the server, and in those cases they're typically named  or.

Every module has one main file. This is the first file listed, unless another file is explicitly designated as the main file. When the module loads, only the code in the main file is executed. The code in the other files is not executed unless and until it is invoked using.

To invoke a non-main file, use. The path must start with  or   to indicate that it's a file in the same module (as opposed to the name of another module), and the path must be relative to the current file. The return value of   is the value that foo.js assigns to.

Module definition
Files can be specified in the  property in the following ways:


 * will load a file from the filesystem
 * will load the file  from the file system, but its alias will be   (meaning it will be known under that name that for the purposes of  )
 * explicitly designates this file as the main file (the file that is executed first). If no main file is explicitly designated, the first file is the main file
 * To designate an aliased file as the main file, use
 * To designate a dynamic file as the main file, use ;   and   work similarly
 * defines a dynamic file called, with the specified contents
 * defines a dynamic file whose contents are
 * defines a dynamic file whose contents are the result of executing the callback
 * You can also specify a static function as a callback:  will call
 * For JS files, the callback should return a string. For JSON files, it can return anything that's JSON-serializable (typically an associative array)
 * The callback is executed in the context of a load.php request, so it doesn't know which user is logged in or which page is being viewed; instead, the result is computed once and reused across different users and pages. If you attempt to use the current user in your callback, you will likely get a  error. You should not use RequestContext in this callback. The ResourceLoaderContext object passed into the callback should have all the getters you need, including a   method to get a Config object, and a   method to format i18n messages.
 * defines a dynamic file whose contents are . Note that this syntax uses config setting names as understood by   (e.g.  ), not the names of global variables  )
 * You can use aliases to export configuration variables under different names:  will result in
 * If you need to do more advanced manipulation of config variables, use a callback as described above. In the callback, you can use e.g.  to get the value of a config setting.

Base path
Most package modules set  to the common directory prefix of the files. This is done for convenience (not having to write  over and over), and to make dynamic files easier to deal with. Without a base path, a dynamic file called  would have to be accessed using , or it would have to be named   so it could be accessed using. With a base path, you get the best of both worlds.

Note that, if you set, you will also have to set   (for core) or   (for extensions) to match.

Incompatibility with
If a module uses the  property, it cannot use the   property. Defining a module that uses both properties will throw an exception.

Package modules are also incompatible with  and. Defining these properties won't throw an exception, but they will be ignored. A way of defining language/skin-specific script files for package modules has not yet been developed (and this blocks porting ResourceLoaderLanguageDataModule).

Example uses in real code

 * ProtectionForm: Move JS config var to RL packageFiles
 * Use packageFiles feature to replace special-purpose RL modules
 * RCFilters: Export config vars in the RL modules where possible (see also the revert and fix)
 * [ your patches here! ]

Module definition (core)
In Resources.php:

Module definition (extension)
In extension.json: In MyExtensionHooks.php:

JavaScript
In init.js: In thinglib/index.js: In thinglib/formatter.js: