From: Timo Tijhof Date: Thu, 12 Jul 2018 20:09:28 +0000 (-0700) Subject: resourceloader: Combine base modules and page modules requests X-Git-Tag: 1.34.0-rc.0~4519^2 X-Git-Url: http://git.cyclocoop.org/%28?a=commitdiff_plain;h=dec800968eb618c1f8632df7be49783fc82078d2;p=lhc%2Fweb%2Fwiklou.git resourceloader: Combine base modules and page modules requests This commit implements step 4 and step 5 of the plan outlined at T192623. Before this task began, the typical JavaScript execution flow was: * HTML triggers request for startup module (js req 1). * Startup module contains registry, site config, and triggers a request for the base modules (js req 2). * After the base modules arrive (which define jQuery and mw.loader), the startup module invokes a callback that processes RLQ, which is what will request modules for this page (js req 3). In past weeks, we have: * Made mediawiki.js independent of jQuery. * Spun off 'mediawiki.base' from mediawiki.js – for everything that wasn't needed for defining `mw.loader`. * Moved mediawiki.js from the base module request to being embedded as part of startup.js. The concept of dependencies is native to ResourceLoader, and thanks to the use of closures in mw.loader.implement() responses, we can download any number of interdependant modules in a single request (or parallel requests). Then, when a response arrives, mw.loader takes care to pause or resume execution as-needed. It is normal for ResourceLoader to batch several modules together, including their dependencies. As such, we can eliminate one of the two roundtrips required before a page can request modules. Specifically, we can eliminate "js req 2" (above), by making the two remaining base modules ("jquery" and "mediawiki.base") an implied dependency for all other modules, which ResourceLoader will naturally fetch and execute in the right order as part of the batch request. Bug: T192623 Change-Id: I17cd13dffebd6ae476044d8d038dc3974a1fa176 --- diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index a0acf1f30b..637bae640a 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -1484,7 +1484,7 @@ MESSAGE; } /** - * Wraps JavaScript code to run after startup and base modules. + * Wraps JavaScript code to run after the startup module. * * @param string $script JavaScript code * @return string JavaScript code diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php index a4fd71233f..53c10df4b1 100644 --- a/includes/resourceloader/ResourceLoaderStartUpModule.php +++ b/includes/resourceloader/ResourceLoaderStartUpModule.php @@ -319,17 +319,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { return true; } - /** - * @param ResourceLoaderContext $context - * @return array - */ - public function getPreloadLinks( ResourceLoaderContext $context ) { - $url = $this->getBaseModulesUrl( $context ); - return [ - $url => [ 'as' => 'script' ] - ]; - } - /** * Internal modules used by ResourceLoader that cannot be depended on. * @@ -353,10 +342,18 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { return []; } + /** + * @private For internal use by SpecialJavaScriptTest + * @since 1.32 + * @return array + */ + public function getBaseModulesInternal() { + return $this->getBaseModules(); + } + /** * Base modules implicitly available to all modules. * - * @since 1.32 * @return array */ private function getBaseModules() { @@ -370,25 +367,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { return $baseModules; } - /** - * Get the load URL of the startup modules. - * - * This is a helper for getScript(). - * - * @param ResourceLoaderContext $context - * @return string - */ - private function getBaseModulesUrl( ResourceLoaderContext $context ) { - $rl = $context->getResourceLoader(); - $derivative = new DerivativeResourceLoaderContext( $context ); - $derivative->setModules( $this->getBaseModules() ); - $derivative->setOnly( 'scripts' ); - // Must setModules() before makeVersionQuery() - $derivative->setVersion( $rl->makeVersionQuery( $derivative ) ); - - return $rl->createLoaderURL( 'local', $derivative ); - } - /** * @param ResourceLoaderContext $context * @return string JavaScript code @@ -399,35 +377,43 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { return '/* Requires only=script */'; } - $out = file_get_contents( "$IP/resources/src/startup/startup.js" ); + $startupCode = file_get_contents( "$IP/resources/src/startup/startup.js" ); - // Keep in sync with maintenance/jsduck/eg-iframe.html and, - // keep in sync with 'fileHashes' in StartUpModule::getDefinitionSummary(). + // The files read here MUST be kept in sync with maintenance/jsduck/eg-iframe.html, + // and MUST be considered by 'fileHashes' in StartUpModule::getDefinitionSummary(). $mwLoaderCode = file_get_contents( "$IP/resources/src/startup/mediawiki.js" ) . file_get_contents( "$IP/resources/src/startup/mediawiki.requestIdleCallback.js" ); if ( $context->getDebug() ) { $mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/mediawiki.log.js" ); } - $pairs = array_map( function ( $value ) { + $mapToJson = function ( $value ) { $value = FormatJson::encode( $value, ResourceLoader::inDebugMode(), FormatJson::ALL_OK ); // Fix indentation $value = str_replace( "\n", "\n\t", $value ); return $value; - }, [ + }; + + // Perform replacements for mediawiki.js + $mwLoaderCode = strtr( $mwLoaderCode, [ + '$VARS.baseModules' => $mapToJson( $this->getBaseModules() ), + ] ); + + // Perform replacements for startup.js + $pairs = array_map( $mapToJson, [ '$VARS.wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ), '$VARS.configuration' => $this->getConfigSettings( $context ), - // This url may be preloaded. See getPreloadLinks(). - '$VARS.baseModulesUri' => $this->getBaseModulesUrl( $context ), ] ); + // Raw JavaScript code (not for JSON) $pairs['$CODE.registrations();'] = str_replace( "\n", "\n\t", trim( $this->getModuleRegistrations( $context ) ) ); $pairs['$CODE.defineLoader();'] = $mwLoaderCode; + $startupCode = strtr( $startupCode, $pairs ); - return strtr( $out, $pairs ); + return $startupCode; } /** diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php index b786c869fb..f858f5c2cd 100644 --- a/includes/specials/SpecialJavaScriptTest.php +++ b/includes/specials/SpecialJavaScriptTest.php @@ -103,65 +103,56 @@ class SpecialJavaScriptTest extends SpecialPage { $query['only'] = 'scripts'; $startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) ); - $query['raw'] = true; - $modules = $rl->getTestModuleNames( 'qunit' ); // Disable autostart because we load modules asynchronously. By default, QUnit would start // at domready when there are no tests loaded and also fire 'QUnit.done' which then instructs - // Karma to end the run before the tests even started. + // Karma to exit the browser process before the tests even finished loading. $qunitConfig = 'QUnit.config.autostart = false;' . 'if (window.__karma__) {' // karma-qunit's use of autostart=false and QUnit.start conflicts with ours. - // Hack around this by replacing 'karma.loaded' with a no-op and call it ourselves later. - // See . + // Hack around this by replacing 'karma.loaded' with a no-op and perfom its duty of calling + // `__karma__.start()` ourselves. See . . 'window.__karma__.loaded = function () {};' . '}'; // The below is essentially a pure-javascript version of OutputPage::headElement(). - $startup = $rl->makeModuleResponse( $startupContext, [ + $code = $rl->makeModuleResponse( $startupContext, [ 'startup' => $rl->getModule( 'startup' ), ] ); - // Embed page-specific mw.config variables. - // The current Special page shouldn't be relevant to tests, but various modules (which - // are loaded before the test suites), reference mw.config while initialising. - $code = ResourceLoader::makeConfigSetScript( $out->getJSVars() ); - // Embed private modules as they're not allowed to be loaded dynamically - $code .= $rl->makeModuleResponse( $embedContext, [ - 'user.options' => $rl->getModule( 'user.options' ), - 'user.tokens' => $rl->getModule( 'user.tokens' ), - ] ); - // Catch exceptions (such as "dependency missing" or "unknown module") so that we - // always start QUnit. Re-throw so that they are caught and reported as global exceptions - // by QUnit and Karma. - $modules = Xml::encodeJsVar( $modules ); - $code .= <<getJSVars() ) + // Embed private modules as they're not allowed to be loaded dynamically + . $rl->makeModuleResponse( $embedContext, [ + 'user.options' => $rl->getModule( 'user.options' ), + 'user.tokens' => $rl->getModule( 'user.tokens' ), + ] ) + // Load all the test suites + . Xml::encodeJsCall( 'mw.loader.load', [ $modules ] ) + ); + $encModules = Xml::encodeJsVar( $modules ); + $code .= ResourceLoader::makeInlineCodeWithModule( 'mediawiki.base', <<