Jump to content

User:Evad37/Script modules

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Evad37 (talk | contribs) at 04:30, 26 January 2019 (The code: clarify). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

Script modules are bits of Javascript code intended to be easily reused by userscripts. Script modules are to be stored in the MediaiWiki: namespace, as subpages of MediaWiki:Script module. A list of all script modules will thus be available at Special:PrefixIndex/MediaWiki:Script module/.

The basics

A new on-by-default gadget would add the functions exportScriptModule and importScriptModule to the Window object.

Pseudo-namespace

Script modules are to be stored in the pseudo-namespace MediaWiki:Script module/. This means that only code approved by Interface Admins can be exported to other scripts. However, script module authors should not expect a full code review from an Interface Admin: they are only required to do basic security and privacy checks (but may do further checks at their discretion). Thus, script module code should only be considered slightly more secure or reliable than your average userscript.

Module authors wanting to add or update a script module should use an edit request, or post a request at the Interface administrators' noticeboard.

For script modules

Script modules use the syntax:

Window.exportScriptModule('name', IIFE);

where name is the name of script module (subpage name without the .js suffix), and IIFE is an immediately invoked function expression which returns the function or object to be exported. All of the script module's code is to be placed within the IIFE.

For userscripts

Userscripts use the syntax:

Window.importScriptModule('name').then(function(localVar) {
    /* ... code using the script module's export ... */
});

where name is the name of script module (subpage name without the .js suffix), and localVar is a local variable name for whatever the script module exported.

Just like mw.loader, the importScriptModule function expresses the intent that a script module be loaded. A new network request is not actually sent if that script module has already been loaded by another script.

Worked example

Suppose you want some code to parse wikitext for templates, and return an array of nicely formatted objects like:

{
    name: 'nameOfTemplate',
    wikitext: '{{nameOfTemplate|foo|2=1+1=2|bar=qux}}',
    parameters: [
        {name: 1, value:'foo'},
        {name:'2', value:'1+1=2'},
        {name:'bar', value:'qux'}
    ]
}

The module code would stored at MediaWiki:Script module/templateParser.js and contain something like:

Window.exportScriptModule('templateParser', (function() {
        /* ... helper functions omitted ... */
    var templateParser = function(wikitext) {
        /* ... lots of lines omitted ... */
        return result;
    };
    return templateParser;
})());

A userscript would access the template parser using code like this:

importScriptModule('templateParser').then(function(tParser) {
    // `tParser` is our local variable name for the module's `templateParser` function. We can choose any name we want.
    // We can now use the code defined in that module, e.g.
    var templateInfo = tParser('==See also==\n{{Commons category}}\n{{Portal-inline|JavaScript|size=tiny}}\n*[[JavaScript templating]]');
    /* `templateInfo` now evaluates to:
    [
        {
            name: 'Commons category',
            wikitext: '{{Commons category}}',
            parameters: []
        },
        {
            name: 'Portal-inline',
            wikitext: '{{Portal-inline|JavaScript|size=tiny}}',
            parameters: [
                {name: 1, value:'JavaScript'},
                {name:'size', value:'tiny'}
            ]
        }
    ]
    */
};
// `tParser` is not available out here. The scope is limited to the function within .then()

Loading multiple script modules

To load several modules, use $.when(), e.g.

$.when(
    importScriptModule('templateParser'),
    importScriptModule('multiButtonConfirm')
).then(function(tParser, multiConfirm) {
        /* ... code using these modules goes here ... */
});

Loading errors

If there is an error loading a script module, the promise returned by importScriptModule will be rejected with an error message. Access the error message using a "failFilter" function in .then() e.g.

importScriptModule('moduleWithAnError').then(
    function(foo) {
        /* ... code that would be executed, if not for the loading error ... */
    }),
    function(failMessage) {
        /* Show an error message to the user. Perhaps using bubble notifications from mw.notify */
    }
);

The code

The code for the script enabling the exportScriptModule and importScriptModule functions would be

Window.exportScriptModule = function(name, exported) {
	Window.exportScriptModule[name] = exported;
};
Window.importScriptModule = function(module) {
     return mw.loader.using('mediawiki.util').then(function() {
         if ( Window.exportScriptModule[module] === undefined ) {
             // Load module
             return $.getScript(
                 'https://en.wikipedia.org/w/index.php?title=MediaWiki:Script module' +
                 mw.util.wikiUrlencode(module) +
                 '.js&action=raw&ctype=text/javascript'
             );
         }
         // else module has already been loaded
         return true;
     })
     .then(
     	function() {
        	if ( Window.exportScriptModule[module] === undefined ) {
            	throw new Error("Script module " + module + " did not export anything!");
        	}
        	return Window.exportScriptModule[module];
    	},
    	function() {
        	 throw new Error("Failed to load script module " + module);
    	}
    );
};

This should probably be an on-by-default gadget for registered users, as it needs to be loaded before any userscripts are loaded, but is only useful for registered users.

Demos

Here are some demonstrations, using Evad37's subpages instead of MediaWiki namespace.

Add the following code to your Special:MyPage/common.js to see these demonstrations working:

$.getScript('https://en.wikipedia.org/w/index.php?title=User:Evad37/ScriptModules.js&action=raw&ctype=text/javascript')
.then(function() {
	importScript('User:Evad37/ScriptModulesDemo.js');
	importScript('User:Evad37/ScriptModulesDemo2.js');
	importScript('User:Evad37/ScriptModulesDemo3.js');
});