resourceloader: Implement modern module loading (1/2)
This defines mw.loader.require() and 'module.exports'. These will be exposed to mw.loader.implement() closures as local 'require' and 'module' parameters. Changes: * This alters nestedAddScript to maintain a single queue to ensure scripts from different modules are never downloaded in parallel (used in debug mode). Note: A further patch will start passing module and require to module definitions. Bug: T108655 Change-Id: Ia925844cc22f143f531216f2fe3efead08885b5d
This commit is contained in:
parent
db47c86bc5
commit
94c1162400
3 changed files with 115 additions and 3 deletions
|
|
@ -20,6 +20,8 @@
|
|||
"browser": true,
|
||||
|
||||
"globals": {
|
||||
"require": false,
|
||||
"module": false,
|
||||
"mediaWiki": true,
|
||||
"JSON": true,
|
||||
"OO": true,
|
||||
|
|
|
|||
|
|
@ -714,6 +714,7 @@
|
|||
* 'group': 'somegroup', (or) null
|
||||
* 'source': 'local', (or) 'anotherwiki'
|
||||
* 'skip': 'return !!window.Example', (or) null
|
||||
* 'module': export Object
|
||||
*
|
||||
* // Set from execute() or mw.loader.state()
|
||||
* 'state': 'registered', 'loaded', 'loading', 'ready', 'error', or 'missing'
|
||||
|
|
@ -767,6 +768,10 @@
|
|||
// List of modules which will be loaded as when ready
|
||||
batch = [],
|
||||
|
||||
// Pending queueModuleScript() requests
|
||||
handlingPendingRequests = false,
|
||||
pendingRequests = [],
|
||||
|
||||
// List of modules to be loaded
|
||||
queue = [],
|
||||
|
||||
|
|
@ -1176,6 +1181,43 @@
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue the loading and execution of a script for a particular module.
|
||||
*
|
||||
* @private
|
||||
* @param {string} src URL of the script
|
||||
* @param {string} [moduleName] Name of currently executing module
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
function queueModuleScript( src, moduleName ) {
|
||||
var r = $.Deferred();
|
||||
|
||||
pendingRequests.push( function () {
|
||||
if ( moduleName && !hasOwn.call( registry, moduleName ) ) {
|
||||
window.require = mw.loader.require;
|
||||
window.module = registry[ moduleName ].module;
|
||||
}
|
||||
addScript( src ).always( function () {
|
||||
// Clear environment
|
||||
delete window.require;
|
||||
delete window.module;
|
||||
r.resolve();
|
||||
|
||||
// Start the next one (if any)
|
||||
if ( pendingRequests[ 0 ] ) {
|
||||
pendingRequests.shift()();
|
||||
} else {
|
||||
handlingPendingRequests = false;
|
||||
}
|
||||
} );
|
||||
} );
|
||||
if ( !handlingPendingRequests && pendingRequests[ 0 ] ) {
|
||||
handlingPendingRequests = true;
|
||||
pendingRequests.shift()();
|
||||
}
|
||||
return r.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for execute()
|
||||
*
|
||||
|
|
@ -1226,7 +1268,7 @@
|
|||
handlePending( module );
|
||||
};
|
||||
nestedAddScript = function ( arr, callback, i ) {
|
||||
// Recursively call addScript() in its own callback
|
||||
// Recursively call queueModuleScript() in its own callback
|
||||
// for each element of arr.
|
||||
if ( i >= arr.length ) {
|
||||
// We're at the end of the array
|
||||
|
|
@ -1234,7 +1276,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
addScript( arr[ i ] ).always( function () {
|
||||
queueModuleScript( arr[ i ], module ).always( function () {
|
||||
nestedAddScript( arr, callback, i + 1 );
|
||||
} );
|
||||
};
|
||||
|
|
@ -1249,8 +1291,9 @@
|
|||
} else if ( $.isFunction( script ) ) {
|
||||
// Pass jQuery twice so that the signature of the closure which wraps
|
||||
// the script can bind both '$' and 'jQuery'.
|
||||
script( $, $ );
|
||||
script( $, $, mw.loader.require, registry[ module ].module );
|
||||
markModuleReady();
|
||||
|
||||
} else if ( typeof script === 'string' ) {
|
||||
// Site and user modules are legacy scripts that run in the global scope.
|
||||
// This is transported as a string instead of a function to avoid needing
|
||||
|
|
@ -1742,6 +1785,11 @@
|
|||
}
|
||||
// List the module as registered
|
||||
registry[ module ] = {
|
||||
// Exposed to execute() for mw.loader.implement() closures.
|
||||
// Import happens via require().
|
||||
module: {
|
||||
exports: {}
|
||||
},
|
||||
version: version !== undefined ? String( version ) : '',
|
||||
dependencies: [],
|
||||
group: typeof group === 'string' ? group : null,
|
||||
|
|
@ -2009,6 +2057,26 @@
|
|||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the exported value of a module.
|
||||
*
|
||||
* Module provide this value via their local `module.exports`.
|
||||
*
|
||||
* @since 1.27
|
||||
* @return {Array}
|
||||
*/
|
||||
require: function ( moduleName ) {
|
||||
var state = mw.loader.getState( moduleName );
|
||||
|
||||
// Only ready mudules can be required
|
||||
if ( state !== 'ready' ) {
|
||||
// Module may've forgotten to declare a dependency
|
||||
throw new Error( 'Module "' + moduleName + '" is not loaded.' );
|
||||
}
|
||||
|
||||
return registry[ moduleName ].module.exports;
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc mw.inspect#runReports
|
||||
* @method
|
||||
|
|
|
|||
|
|
@ -1085,4 +1085,46 @@
|
|||
);
|
||||
} );
|
||||
|
||||
QUnit.test( 'mw.loader.require', 6, function ( assert ) {
|
||||
var module1, module2, module3, module4;
|
||||
|
||||
mw.loader.register( [
|
||||
[ 'test.module.require1', '0' ],
|
||||
[ 'test.module.require2', '0' ],
|
||||
[ 'test.module.require3', '0' ],
|
||||
[ 'test.module.require4', '0', [ 'test.module.require3' ] ]
|
||||
] );
|
||||
mw.loader.implement( 'test.module.require1', function () {} );
|
||||
mw.loader.implement( 'test.module.require2', function ( $, jQuery, require, module ) {
|
||||
module.exports = 1;
|
||||
} );
|
||||
mw.loader.implement( 'test.module.require3', function ( $, jQuery, require, module ) {
|
||||
module.exports = function () {
|
||||
return 'hello world';
|
||||
};
|
||||
} );
|
||||
mw.loader.implement( 'test.module.require4', function ( $, jQuery, require, module ) {
|
||||
var other = require( 'test.module.require3' );
|
||||
module.exports = {
|
||||
pizza: function () {
|
||||
return other();
|
||||
}
|
||||
};
|
||||
} );
|
||||
module1 = mw.loader.require( 'test.module.require1' );
|
||||
module2 = mw.loader.require( 'test.module.require2' );
|
||||
module3 = mw.loader.require( 'test.module.require3' );
|
||||
module4 = mw.loader.require( 'test.module.require4' );
|
||||
|
||||
assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
|
||||
assert.strictEqual( module2, 1, 'export a number' );
|
||||
assert.strictEqual( module3(), 'hello world', 'export a function' );
|
||||
assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
|
||||
assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
|
||||
|
||||
assert.throws( function () {
|
||||
mw.loader.require( '_badmodule' );
|
||||
}, /is not loaded/, 'Requesting non-existent modules throws error.' );
|
||||
} );
|
||||
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
Loading…
Reference in a new issue