jenkins-bot has submitted this change and it was merged.
Change subject: resourceloader: Implement modern module loading (1/2)
......................................................................
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
---
M .jshintrc
M resources/src/mediawiki/mediawiki.js
M tests/qunit/suites/resources/mediawiki/mediawiki.test.js
3 files changed, 115 insertions(+), 3 deletions(-)
Approvals:
Krinkle: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.jshintrc b/.jshintrc
index 62b9d82..441c4e3 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -20,6 +20,8 @@
"browser": true,
"globals": {
+ "require": false,
+ "module": false,
"mediaWiki": true,
"JSON": true,
"OO": true,
diff --git a/resources/src/mediawiki/mediawiki.js
b/resources/src/mediawiki/mediawiki.js
index 2aada9e..f282db6 100644
--- a/resources/src/mediawiki/mediawiki.js
+++ b/resources/src/mediawiki/mediawiki.js
@@ -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'
@@ -766,6 +767,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 = [],
@@ -1177,6 +1182,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()
*
* @ignore
@@ -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,
@@ -2010,6 +2058,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
*/
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
index fe5530b..ce4ea8b 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
@@ -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 ) );
--
To view, visit https://gerrit.wikimedia.org/r/260071
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ia925844cc22f143f531216f2fe3efead08885b5d
Gerrit-PatchSet: 36
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Jdlrobson <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Bartosz DziewoĆski <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Edokter <[email protected]>
Gerrit-Reviewer: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits