User:✓/RL 3.0

This page is something like a general overview of my vision of mediawikis javascript infrastructure. It's a working sheet, feel free to contribute and/or discuss.

I've also tried to link nearly every related bugzilla entry (lots of them with contribs of mine, some already closed or even wontfixed) that I could find. I'm pleased to get more links to discussions.

Pros edit

The ResourceLoader is a cool thing, no question, and has brought many improvements (see also requirements):

Contras edit

But there are also lots of drawbacks, especially when used for user scripts - of course the high traffic of anonymous users is handled already. This list may extend the task list of ResourceLoader/Version 2 Design Specification (better: Version 2.1 todo):

  • no way to make full use of ResourceLoader for user-level scripts (i.e. a replacement for the inefficient action=raw) (bug 34958 (Allow creation of ResourceLoader wiki-modules on a user level))
  • no support for internationalisation/customisation, the former only when it's a core/extension-added script. See also #customisation
  • no support for packaging userpages
  • the load/ready events for modules are not public. Many scripts could use such hooks: de:Benutzer:Schnark/js/wikieditor.js needs to check for existence of window.wikieditor, the extra-editbuttons-gadgets uses very queer magic to execute in the right time, a script that extends the functionality of enhancedRecentChanges and expandedWatchList... just an example how I tried to code the last one:
jQuery(document).ready(function ($) { // forgot using(['mediawiki.user', 'user.options'], ...
    if ( (function () {
        var o = {"Recentchanges": "usenewrc", "Watchlist": "extendwatchlist"}[mw.config.get('wgCanonicalSpecialPageName')];
        if ( !o ) return false;
        if (mw.user && mw.user.options) {
            o = mw.user.options.get(o);
            return o === null || o === undefined || o === '1';
        }
        return true;
    }()) ) {
        // do something
    }
});
Bug 28995 - Public interface for getting loader state of a ResourcerLoader module (mine :-) was a step in the right direction, but not far enough I had to find out.

deliberations edit

  • Most scripts are not even needed when loaded. Many begin with something like
mw.loader.using(["something"], function() { jQuery( function() {
    if (mw.config.get('wgAction') == "edit")
        go(); // OK, exaggerated
    else
        return; // do nothing
    function go() { ...
But still, the whole script is loaded for each request. So two things for better code:
  • define only one function, not two or three event listeners. Don't define a function at all, just write a script which is automatically wrapped in a function.
  • define the rest somwhere else: dependencies, start, requirements

solution? edit

To come to a conclusion: user scripts and gadgets don't differ really from each other. They have the same aim, only gadgets are more popular. Therefore, gadgets may be presented in a selective list, while userscripts need to be searched for and added dynamically by typing their name into the preferences form.

So I'd propose to set up the following infrastructure:

  • We have a Specialpage "DefineResourceLoaderModule", needing no more rights than autoconfirmed.
  • you can define modules there, prefixed with the namespace "user.<username>."
  • If you have "gadgeteditor" rights (usually in group sysop, maybe an extra group), you can define modules with the prefix "gadget."
  • not sure whether to kick MediaWiki:gadgets-definition or not and show just all gadget modules. I think it's better to remove all the RL-options from it, and add the possibility to define internal and external gadget-modules. I can see no need for a "Gadget Manager", as proposed, in my vision setup.
  • All module settings are public, editable only by those who have the rights ("own" and "edituserjscss" for user modules, "gadgeteditor" for gadgets).
  • a module definition includes:
    name
    that identifier with the prefix (shouldn't change?)
    short description
    If it's a gadget, that description should be saved in MediaWiki Namespace (as usual, with the "name"), where it can be localised (rights "editinterface", "gadgettranslator"?). Userscripts dont get translations?
    link to a help page
    For userscripts, this defaults to the script/stylesheet subpage without suffix.
    • What about user js/css which have subpages/submodules? Should "User:Name/Foo.js/bar.js" be documented at "User:Name/Foo.js/bar" (no back link to "User:Name/Foo" on contentSub), "User:Name/Foo/bar" (backlink to "User:Foo") or "User:Name/Foo" (central documentation)?
      There is no User:Foo.js. A module "[iw-prefix]user.foo.bar", located usually at User:Foo/bar.js, is considered to be documented at User:Foo/bar. A sub-script should be located at User:Foo/bar/x.js (or User:Foo/bar.js/x.js, sometimes? — you're free to choose the scheme) may be named "user.foo.bar.x"-module, documented at User:Foo/bar[.js]/x. You can change all of these references, set up redirects to centralized docs or do anything else. For example scripts located at User:Foo/js/x.js and User:Foo/js/y.js may be loaded together as module "user.foo.z-framework.init", and be documented at User:Foo/my_library/js or something. --Bergi (talk) 16:46, 2 March 2012 (UTC)
    activation conditions
    a set of conditions when to activate the module (see below), might be just a js function, or a boolean variable/expression
    list of scripts and stylesheets
    Not to be forgotten :-) Their default names are the module name plus ".js"/".css" suffix, in User- respectively MediaWiki namespace.
    • each script has a start/position: in which loading state the function should be executed:
    • also stylesheets need position information, like "not async". Most stylesheets are to be added for different conditions, yes, but not to be loaded dynamic or inside a json file (sounds crazy, but is done?!). Instead they need to be added to head, where they are loaded (combinded and minimized, of course) to influence static pages. Added serverside, to bring styling options with RL modules to noscript users - don't forget them!
    modules to be prefetched
    Some modules may be loaded just in need, like jquery.gui. They are not a dependency of the specified module though, but they're should load info should be registrated.
    translation
    and of course, #3 type of resources; not applicable today. See also #customisation
    • you can add native systemmessages, maybe even with wildcards - don't use the MediaWiki ns for tool-specific messages. These messages might get loaded from the requester's wiki, even if the module is used as an external one.
    • add the name[s] of [a] page[s], whose iso-named subpages contain a (JSON) key-value-map. These pagenames must be user subpages or MediaWiki pages for gadgets. The data-pages are editable both with "gadgeteditor", "gadgettranslator" (and "editinterface"/"sysop") rights. There might be a nice frontend for declaring new or deprecating message names and translating, instead of editing json files.
    • To be exakt: for each of the translation definition(-set)s you should be able to choose whether the localisation is done on "contentlang" or "uselang". See also 29873 (Add support for content language for messages in ResourceLoader)25349 (Resource loader should allow loading messages in nonstandard ways)
  • each of the so-defined modules will build a resource-loader-group. It may be merged to another group when the enhance-other-module load condition is set.
  • How to output that? When a page is requested, ResourceLoader will get all addModule()s and user-activated modules, resolve their dependencies and save that list. On the flush of the output queue, RL will write a script containing the registration (with group, version, depencies, conditions), topmodules and the load() call.
  • It might happen, that using() or other dependency methods don't find a requested module registered. So then they will complete their work, then file a request with all unknown modules they found. The answer will include the registrations (just as the modules were added in the beginning), after that the methods can go on checking the conditions and eventually load the modules. Will that extra request get caching problems?
  • As said above, all gadgets are available in the respective section of Special:Preferences as a list, with their description, just to select with a checkbox - as already today. Then there is a pref section "custom modules". Here you can add any valid module names - own userscriptmodules (these already listed?), foreign usermodules, external (interwiki) usermodules and gadgets. A nice autocomplete, and when valid the description shows up :-)
  • The old /common.js, /<skin>.js, /common.css and /<skin>.css might work on. Or we autoconvert them into user modules, being loaded on-skin and in-head.
  • There should be some statistics, if not even public user settings about module usage. Today it is desired to add a out-commented wikilink behind every importScript() call, give the script author a) an indication how widespread it is used and b) the possibility to inform users about breaking changes (especially in customisation). To publish the number of uses should be no problem, and we could offer a select for user modules (X) disable chance to get informed ( ) show my usage to script author[s] ( ) I'm proud of using this, show it everybody

The load and execution conditions may contain:

  • nothing, to indicate the module is to be loaded always, or:
  • just a pointer to another module which we want to enhance, i.e. load alongside when it gets loaded
  • a flag whether the module is allowed to load on "secure" pages like Preferences or Login →Bug 10005 (User modules not loaded on pages that disallow module origin "ORIGIN_CORE_INDIVIDUAL"). At least, I think, with today's JS it would be possible to completely simulate such pages anyway, so we might not need that any more
  • the dependencies to be fulfilled before execution (handle onReady as a dependency?)
  • the usual $wgPageState: wgPageName, wgSkin, wgCanonicalSpecialPageName, wgAction, wgNamespaceId etc
  • user.optionsuser.customisation, I mean - options sounds useless
  • user.rights - interesting when I dynamically switch to my bot account

→ or simply define a js function directly instead of the last three points, as any gui-definable logic won't be complex enough for some things. Otherwise a js function would be built out of that gui-defined logic.

You might want to have a look at Schnarks jsmodules, a nice workaround for RL. In de:Benutzer:Schnark/js/modules.js you find the module definitions: version, link, description, dependencies, and some load condition functions.

customisation edit

I'm the sort of guy who wants to be able to change everything. From different toolbar icons up to a fully converted skin, it should be 1 script, many options. A really great example is User:PerfektesChaos/js/editToolStrIns#User customization and project adaption. It does not only have everything you want, it runs into any problems you could run in (and survives :-).

I figured out that there are two directions: internationalisation/localisation and generalisation/customisation. You also have two types of variables in your script: messages and options.

<msg-not-found> en de bar ← fallback language
MediaWiki
wiki project
user wish
↓ customisation
Messages
come from wfGetMessage() respectively api.php?action=query&meta=allmessages of the current wiki, if they are system messages; which means that they are automatically fall-backed (unfortunately first from project to MW-default, then from bar to de - I'm not really lucky with that, nor anyone else is) and can be edited in site (wiki) scope. But modules may also store tool-specific messages in their own structures (described above). I'd propose them to be simple, complete JSON files, but when we allowed partial translation with fallbacks that would need an extra API / RL feature.
Sometimes, it might happen that especially external, imported messages need an site-scoped "update". I'm not sure if we could implement that with the same translation mechanism - any onwiki-defintion would be another module, maybe with dependencies? -, but there must to be a way to integrate some customised messages. At last, users would like to set up a script/module to customise messages on a per-user-basis; maybe not even language-specific but for all translations.
Options
are any other values that do not need to be translated. That could make it much easier, but it isn't, because options usually are complex objects. The default options can be hardcoded into the module logic, if the script is well-modularized they are exposed as a config object. The situation is quite the same, both at wiki and user scope there are settings to be integrated. But exactly that's what makes it difficult: integrate, not overwrite (as like for messages).
If the options were just one simple object, you might call Object.extend() on it, done. If we had subobjects, we might use something like Object.merge(), as this doesn't overwrite object properties. But still it isn't suitable for maps (key-value-pairs), where we might want to remove keys but hold their values, in case the keys are added again by a more specific customisation. Experienced scripters would have made the value Object one setting (to merge in), and the keys another setting, an (ordered!) Array to overwrite/manipulate.

These config manipulations and language-fallback operations have to be handled by every script that does more than serving a library, and is imho the reason why scripts are not translated and shared. Particularly a config object might have more than 3 different setting integrations, e.g. a foreign script is translated to a different wiki, adapted by User:A, improved by User:B and specialised by User:C. And it gets really heavy if the language affects the options...

Especially because RessourceLoader makes the loading of different scripts (containing the setting options) asynchronous, a user script (most specific) can't be sure any more to be the last to manipulate a config object. Therefore, I think RL must include an interface to create such customisable configurations more easy, staying asynchronous and respect the priorities of different manipulations.