JS2 Overview

From MediaWiki.org

Jump to: navigation, search

JS2 is a new phase of javascript for mediawiki. It contains a script loader that minimizes page loading times by minifying, compressing, and grouping JavaScript files. It provides a unified approach to translation of user messages in JavaScript-based interfaces. It includes javascript helpers for dynamically loading JavaScript libraries that provide interfaces as needed rather than the traditional approach of loading all JavaScript in-line. And finally it promotes structuring javascript as highly modular reusable components, with clean separation of configuration, invocation binding and interface code.

See also Media Projects Overview, MwEmbed.

Contents

[edit] Primary motivation

We need a script server to package language text into JavaScript. When a user invokes an interface component, say the add media wizard, we don't want to pull the entire interface code base and all the messages at once. Instead we just want to grab enough to display the current interface interaction. Once the user click on some tab, say the ‘archive.org search’, we then want to import the code to run the archive.org search and the localized messages specific to that interface component. In other words we don't want to package all the message text in the html output of the initial page because JavaScript interactions can result in many different interfaces being invoked from that context and we only want to load what is absolutely necessary on initial page display/load.

Another example: say you're on a view page. Then click “edit” on a section and we want to do that edit in-line (without going to another page). We now want to load the toolbar JavaScript all its dependences and localized msg text in a single gziped, minified and indefinitely-client-cached by SVN revision request. (Then we want to grab the actual section wiki-text via the API.)

[edit] How to use the ScriptLoader in your extension

You can use the ScriptLoader in your extension with some small modifications:

// Add your script to the JavaScript AutoLoad Class array 
// (using relative paths from mediaWiki root folder, note no ../ traversing is allowed)
$wgJSAutoloadClasses['MyExtension'] =  'extensions/MyExtension/MyExtension.js'; 
// Then anywhere you include your script use:
$wgOut->addScriptClass( 'MyExtension' );
 
// You should *not* use: 
$wgOut->addScript( '<script type="text/javascript" src="$wgScriptPath/extensions/MyExtension/MyExtension.js?$wgStyleVersion" ></script>' );
 
// Note if your JavaScript application is large you may want to load portions of it after initial page load 
//(see [[#How to structure your Javascript Application]]) 
 
// Then when it comes to output time if the script-loader is enabled you get:
<script src="loader.php?class=MyExtension,otherJsClasses,etc&urid=last_modified_time_of_js_files"></script>
 
// If $wgEnableScriptLoader=false you get get script individually similar to the old script call. 
<script type="text/javascript" src="/w/extensions/MyExtension/MyExtension.js?224"></script>

[edit] How to use the ScriptLoader as a gadget developer

As a gadget developer once JS2 is deployed you can write your JavaScript code to include localized messages. It also lets you group a large set of wikipage JavaScript files in a single request and have all the files be gzipped, minified and localized.

Say for example you have quite a few:

importScript('MediaWiki:scriptName1.js')
importScript('MediaWiki:scriptName2.js')
importScript('MediaWiki:scriptName3.js')
//now you could use something like: 
importScript(wgScriptPath + '/mwScriptLoader.php?class=WT:MediaWiki:scriptName1.js,WT:MediaWiki:scriptName2.js,MediaWiki:scriptName3.js');
  • You would probably want to include the urid parameter with a unique id per that script set. When developing if you include ?debug=true you will get a fresh set of the script each time.
  • More advanced usage would involve defined classes and associated JavaScript files, and make use of the mwLoad(['set', 'of' 'class'], function(){ /* Code to run now that the classes are loaded */ }); function. (More on that later once its fully supported for wiki-title namespace.)

[edit] How to include localized messages in your JavaScript

The include msgs for your JavaScript interface must be loaded. Ideally near the header and with one call to loadGM( json_key_msg_pairs ).

loadGM({
    "msg_key":"English text, $1 type replacements are supported. $2, $3, $4"
       ...
})
// Then anytime you want to use your msg in your JavaScript interface: 
alert( gM('msg_key', ['my', 'array', 'of', 'replacements']) );
// Will display alert: 
"English text, my type replacements are supported. array, of, replacements"
Note: The JSON variable you pass to loadGM must be valid JSON (not JavaScript) or PHP will complain. (Valid JSON uses " for keys and values, no newlines, JavaScript concatenations or comments.)

[edit] How does that get translated?

  • Right now you must also manually put the msg in your php localization file (unless your working on the core mwEmbed script set).
  • For mwEmbed scripts and eventually the extensions the translatewiki.net scripts should copy your English key msg into the the php file where its translated with the normal mediaWiki translation system. Then when you load your script via the scriptloader your msg_key will contain the localized msg text.

[edit] JavaScript messages text limitations

Right now we only support basic $1 replace… Things get tricky with contextual wiki-text type replacements. We aim to support a few more translation transformation types. This will involve packing the language replacement php code into something JavaScript can use to do contextual replacements.

For example with 1st 2nd numbers in English we have a mod 10 rule with 11th being an exception. If any of the msgs included the template to format numbers; then we would have php send out a json var something like:

{ 'intNumbers': 
	{ 
		'type' : 'mod10',
		'exceptions' : { '11':'th' },
		'trantype' : {'0':'th', '1':'st', '2':'nd', '3':'rd'}
	}
}

Then the JavaScript parses the msg text and runs some code to interpret the JSON representation then does the dynamic replacement in the msg text.

The above code may look “no fun” but its probably better than the alternatives.

[edit] How to structure your JavaScript application

Js2 applications are ideally structured across a few files.

Lets again consider the example of in-line editing.

For every page we include some core helper functions the wikibits.js of JS2 is presently stored in mv_embed.js. Two functions we are using here are js2AddOnloadHook, mwLoad

  • js2AddOnloadHook is the JS2 version of addOnloadHook. It ensures jquery is available and binds it to $j – to avoid conflicting with other JavaScript libraries that use $; you can still use $ if you wrap your code in the jQuery context (like all the jQuery plugins do).
  • mwLoad function lets us load classes. It will only load classes that are not yet defined. It uses the scriptloader to map class names to JavaScript files and group requests. Say one widget on the page already loaded $j.ui and $j.slider then your app needs $j.ui, $j.slider and $j.fish, your mwLoad call will know to only send a request to the script server for $j.fish. (or directly load the $j.fish.js if scriptloader is off). Once the class are ready it fires the callback (the second argument)

[edit] Example

So for our in-line editing example:

In a file that is included on all pages, we would include something like the following:

js2AddOnloadHook(function(){
	$j('.editsection a').click(function(){
		// Set the section to loading (need to div encapsulate selections) lets say var #sectionTarget
		$j( '#sectionTarget' ).html( mv_get_loading_img() );
 		$j( '#sectionTarget' ).doWikiEditor( {simple config goes here} );
	});	
}
//Your can setup your doWikiEditor loader like so: 
(function( $ ) {
	$.fn.doWikiEditor(config, callback){
		_this = this;
		mwLoad( [ '$j.ui','$j.dialog ','$j.wikiEditor', 'etc'], function(){
			$( _this.selector ).wikiEditor( config);
			if( callback )
				callback();
		});
	}
})(jQuery);

This keeps the core JavaScript to a minimum and loads things as needed and stores the complexity in the reusable JavaScript classes. You can see this method used on scripts in the root of the js2 folder. For info on how configuration could work see: bug 20720 comment about script order

[edit] Configuration variables

$wgEnableJS2system = true; // Enable JS2 at all?
$wgEnableScriptLoader = true; // should script loader be used at all?
$wgDebugJavaScript = false; // If true, JavaScript is not minified and a fresh url 
   // is used for all JS (no need to shift-reload for testing) 
$wgEnableScriptLoaderJsFile = true; // use script loader with js files based on the root script folder
  // (rather than only js classes looked up with $wgEnableScriptLoader)? 
  // E.g.: scriptLoader.php?file=/skins/common/common.js,/skins/common/ajax.js 
$wgScriptModifiedCheck = true; // Should script loader check file modified date of scripts when including them in the page?

See $wgEnableJS2system, $wgEnableScriptLoader, $wgDebugJavaScript,$wgEnableScriptLoaderJsFile, $wgScriptModifiedCheck.

Note: You should “enable” the file cache: $wgUseFileCache = true; or have your wiki behind a proxy. If not, JS2 results in minification, localization and gzipping on every JavaScript request (not fun). Without a file cache it is better to use JS2 without the ScriptLoader. It can fallback to normal JS includes.