resourceloader: Move packaging to a new getModuleContent() method

Centralise the building of module packages into this method so
that it can be easily re-used.

This is in preparation for providing the option for modules to use
content-based hashing (instead of based on meta-data) in the version
string of modules. Having a getModuleContent method allows that to
be implemented in a way that is well-cached (as we'll be calling it
from multiple multiple code paths) and without duplicating this logic.

Bug: T98087
Change-Id: I376233caaabe44b6101565b70a50904abdf8ab4f
This commit is contained in:
Timo Tijhof 2015-05-14 20:05:47 +01:00
parent 50c6cd2458
commit 5ddd7f91c7
2 changed files with 135 additions and 76 deletions

View file

@ -298,8 +298,16 @@ class ResourceLoader implements LoggerAwareInterface {
}
/**
* @param MessageBlobStore $blobStore
* @since 1.26
* @return MessageBlobStore
*/
public function getMessageBlobStore() {
return $this->blobStore;
}
/**
* @since 1.25
* @param MessageBlobStore $blobStore
*/
public function setMessageBlobStore( MessageBlobStore $blobStore ) {
$this->blobStore = $blobStore;
@ -962,7 +970,7 @@ MESSAGE;
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
$blobs = $this->blobStore->get( $this, $modules, $context->getLanguage() );
$this->blobStore->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
$this->logger->warning( 'Prefetching MessageBlobStore failed: {exception}', array(
@ -970,8 +978,6 @@ MESSAGE;
) );
$this->errors[] = self::formatExceptionNoComment( $e );
}
} else {
$blobs = array();
}
foreach ( $missing as $name ) {
@ -981,79 +987,13 @@ MESSAGE;
// Generate output
$isRaw = false;
foreach ( $modules as $name => $module ) {
/**
* @var $module ResourceLoaderModule
*/
try {
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
// If we are in debug mode, we'll want to return an array of URLs if possible
// However, we can't do this if the module doesn't support it
// We also can't do this if there is an only= parameter, because we have to give
// the module a way to return a load.php URL without causing an infinite loop
if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
$scripts = $module->getScriptURLsForDebug( $context );
} else {
$scripts = $module->getScript( $context );
// rtrim() because there are usually a few line breaks
// after the last ';'. A new line at EOF, a new line
// added by ResourceLoaderFileModule::readScriptFiles, etc.
if ( is_string( $scripts )
&& strlen( $scripts )
&& substr( rtrim( $scripts ), -1 ) !== ';'
) {
// Append semicolon to prevent weird bugs caused by files not
// terminating their statements right (bug 27054)
$scripts .= ";\n";
}
}
}
// Styles
$styles = array();
if ( $context->shouldIncludeStyles() ) {
// Don't create empty stylesheets like array( '' => '' ) for modules
// that don't *have* any stylesheets (bug 38024).
$stylePairs = $module->getStyles( $context );
if ( count( $stylePairs ) ) {
// If we are in debug mode without &only= set, we'll want to return an array of URLs
// See comment near shouldIncludeScripts() for more details
if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
$styles = array(
'url' => $module->getStyleURLsForDebug( $context )
);
} else {
// Minify CSS before embedding in mw.loader.implement call
// (unless in debug mode)
if ( !$context->getDebug() ) {
foreach ( $stylePairs as $media => $style ) {
// Can be either a string or an array of strings.
if ( is_array( $style ) ) {
$stylePairs[$media] = array();
foreach ( $style as $cssText ) {
if ( is_string( $cssText ) ) {
$stylePairs[$media][] = $this->filter( 'minify-css', $cssText );
}
}
} elseif ( is_string( $style ) ) {
$stylePairs[$media] = $this->filter( 'minify-css', $style );
}
}
}
// Wrap styles into @media groups as needed and flatten into a numerical array
$styles = array(
'css' => self::makeCombinedStyles( $stylePairs )
);
}
}
}
// Messages
$messagesBlob = isset( $blobs[$name] ) ? $blobs[$name] : '{}';
$content = $module->getModuleContent( $context );
// Append output
switch ( $context->getOnly() ) {
case 'scripts':
$scripts = $content['scripts'];
if ( is_string( $scripts ) ) {
// Load scripts raw...
$out .= $scripts;
@ -1063,6 +1003,7 @@ MESSAGE;
}
break;
case 'styles':
$styles = $content['styles'];
// We no longer seperate into media, they are all combined now with
// custom media type groups into @media .. {} sections as part of the css string.
// Module returns either an empty array or a numerical array with css strings.
@ -1071,10 +1012,10 @@ MESSAGE;
default:
$out .= self::makeLoaderImplementScript(
$name,
$scripts,
$styles,
new XmlJsCode( $messagesBlob ),
$module->getTemplates()
isset( $content['scripts'] ) ? $content['scripts'] : '',
isset( $content['styles'] ) ? $content['styles'] : array(),
isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : array(),
isset( $content['templates'] ) ? $content['templates'] : array()
);
break;
}

View file

@ -64,6 +64,8 @@ abstract class ResourceLoaderModule {
protected $msgBlobMtime = array();
// In-object cache for version hash
protected $versionHash = array();
// In-object cache for module content
protected $contents = array();
// Whether the position returned by getPosition() is defined in the module configuration
// and not a default value
@ -439,6 +441,122 @@ abstract class ResourceLoaderModule {
$this->msgBlobMtime[$lang] = $mtime;
}
/**
* Get an array of this module's resources. Ready for serving to the web.
*
* @since 1.26
* @param ResourceLoaderContext $context
* @return array
*/
public function getModuleContent( ResourceLoaderContext $context ) {
$contextHash = $context->getHash();
// Cache this expensive operation. This calls builds the scripts, styles, and messages
// content which typically involves filesystem and/or database access.
if ( !array_key_exists( $contextHash, $this->contents ) ) {
$this->contents[ $contextHash ] = $this->buildContent( $context );
}
return $this->contents[ $contextHash ];
}
/**
* Bundle all resources attached to this module into an array.
*
* @since 1.26
* @param ResourceLoaderContext $context
* @return array
*/
final protected function buildContent( ResourceLoaderContext $context ) {
$rl = $context->getResourceLoader();
// Only include properties that are relevant to this context (e.g. only=scripts)
// and that are non-empty (e.g. don't include "templates" for modules without
// templates). This helps prevent invalidating cache for all modules when new
// optional properties are introduced.
$content = array();
// Scripts
if ( $context->shouldIncludeScripts() ) {
// If we are in debug mode, we'll want to return an array of URLs if possible
// However, we can't do this if the module doesn't support it
// We also can't do this if there is an only= parameter, because we have to give
// the module a way to return a load.php URL without causing an infinite loop
if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
$scripts = $this->getScriptURLsForDebug( $context );
} else {
$scripts = $this->getScript( $context );
// rtrim() because there are usually a few line breaks
// after the last ';'. A new line at EOF, a new line
// added by ResourceLoaderFileModule::readScriptFiles, etc.
if ( is_string( $scripts )
&& strlen( $scripts )
&& substr( rtrim( $scripts ), -1 ) !== ';'
) {
// Append semicolon to prevent weird bugs caused by files not
// terminating their statements right (bug 27054)
$scripts .= ";\n";
}
}
$content['scripts'] = $scripts;
}
// Styles
if ( $context->shouldIncludeStyles() ) {
$styles = array();
// Don't create empty stylesheets like array( '' => '' ) for modules
// that don't *have* any stylesheets (bug 38024).
$stylePairs = $this->getStyles( $context );
if ( count( $stylePairs ) ) {
// If we are in debug mode without &only= set, we'll want to return an array of URLs
// See comment near shouldIncludeScripts() for more details
if ( $context->getDebug() && !$context->getOnly() && $this->supportsURLLoading() ) {
$styles = array(
'url' => $this->getStyleURLsForDebug( $context )
);
} else {
// Minify CSS before embedding in mw.loader.implement call
// (unless in debug mode)
if ( !$context->getDebug() ) {
foreach ( $stylePairs as $media => $style ) {
// Can be either a string or an array of strings.
if ( is_array( $style ) ) {
$stylePairs[$media] = array();
foreach ( $style as $cssText ) {
if ( is_string( $cssText ) ) {
$stylePairs[$media][] = $rl->filter( 'minify-css', $cssText );
}
}
} elseif ( is_string( $style ) ) {
$stylePairs[$media] = $rl->filter( 'minify-css', $style );
}
}
}
// Wrap styles into @media groups as needed and flatten into a numerical array
$styles = array(
'css' => $rl->makeCombinedStyles( $stylePairs )
);
}
}
$content['styles'] = $styles;
}
// Messages
$blobs = $rl->getMessageBlobStore()->get(
$rl,
array( $this->getName() => $this ),
$context->getLanguage()
);
if ( isset( $blobs[$this->getName()] ) ) {
$content['messagesBlob'] = $blobs[$this->getName()];
}
$templates = $this->getTemplates();
if ( $templates ) {
$content['templates'] = $templates;
}
return $content;
}
/**
* Get a string identifying the current version of this module in a given context.
*