diff --git a/bundlesize.config.json b/bundlesize.config.json index d25bf0abc..be8d1cc7d 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -1,30 +1,42 @@ -[ - { - "resourceModule": "skins.vector.styles.legacy", - "maxSize": "8.4 kB" +{ + "total": { + "vector-2022": { + "scripts": "68.1KB", + "styles": "15KB" + }, + "vector": { + "scripts": "57.3KB", + "styles": "8.3KB" + } }, - { - "resourceModule": "codex-search-styles", - "maxSize": "4.2 kB" - }, - { - "resourceModule": "skins.vector.styles", - "maxSize": "11.2 kB" - }, - { - "resourceModule": "skins.vector.js", - "maxSize": "14.3 kB" - }, - { - "resourceModule": "skins.vector.legacy.js", - "maxSize": "2.5 kB" - }, - { - "resourceModule": "skins.vector.search", - "maxSize": "3.3 kB" - }, - { - "resourceModule": "skins.vector.icons", - "maxSize": "1 kB" - } -] + "modules": [ + { + "resourceModule": "skins.vector.styles.legacy", + "maxSize": "8.4 kB" + }, + { + "resourceModule": "codex-search-styles", + "maxSize": "4.2 kB" + }, + { + "resourceModule": "skins.vector.styles", + "maxSize": "11.2 kB" + }, + { + "resourceModule": "skins.vector.js", + "maxSize": "14.3 kB" + }, + { + "resourceModule": "skins.vector.legacy.js", + "maxSize": "2.5 kB" + }, + { + "resourceModule": "skins.vector.search", + "maxSize": "3.3 kB" + }, + { + "resourceModule": "skins.vector.icons", + "maxSize": "1 kB" + } + ] +} diff --git a/tests/phpunit/structure/BundleSizeTest.php b/tests/phpunit/structure/BundleSizeTest.php index 65832e3ea..cd4968e35 100644 --- a/tests/phpunit/structure/BundleSizeTest.php +++ b/tests/phpunit/structure/BundleSizeTest.php @@ -8,4 +8,12 @@ class BundleSizeTest extends \MediaWiki\Tests\Structure\BundleSizeTest { public function getBundleSizeConfig(): string { return dirname( __DIR__, 3 ) . '/bundlesize.config.json'; } + + /** @inheritDoc */ + public function provideBundleSize() { + $bundleSizeConfig = json_decode( file_get_contents( $this->getBundleSizeConfig() ), true ); + foreach ( $bundleSizeConfig[ 'modules' ] as $testCase ) { + yield $testCase['resourceModule'] => [ $testCase ]; + } + } } diff --git a/tests/phpunit/structure/PerformanceBudgetTest.php b/tests/phpunit/structure/PerformanceBudgetTest.php new file mode 100644 index 000000000..5bfe4f762 --- /dev/null +++ b/tests/phpunit/structure/PerformanceBudgetTest.php @@ -0,0 +1,169 @@ +getResourceLoader(); + $resourceLoader->setDependencyStore( new KeyValueDependencyStore( new HashBagOStuff() ) ); + $request = new FauxRequest( + [ + 'lang' => 'en', + 'modules' => $moduleName, + 'skin' => $skinName, + ] + ); + + $context = new Context( $resourceLoader, $request ); + $module = $resourceLoader->getModule( $moduleName ); + $contentContext = new \MediaWiki\ResourceLoader\DerivativeContext( $context ); + $contentContext->setOnly( + $module->getType() === Module::LOAD_STYLES + ? Module::TYPE_STYLES + : Module::TYPE_COMBINED + ); + // Create a module response for the given module and calculate the size + $content = $resourceLoader->makeModuleResponse( $contentContext, [ $moduleName => $module ] ); + $contentTransferSize = strlen( gzencode( $content, 9 ) ); + // Adjustments for core modules [T343407] + $contentTransferSize -= 17; + return $contentTransferSize; + } + + /** + * Prepares a skin for testing, assigning context and output page + * + * @param string $skinName + * + * @return \Skin + * @throws \SkinException + */ + protected function prepareSkin( string $skinName ): \Skin { + $skinFactory = MediaWikiServices::getInstance()->getSkinFactory(); + $skin = $skinFactory->makeSkin( $skinName ); + $title = Title::newFromText( 'Hello' ); + $context = new DerivativeContext( RequestContext::getMain() ); + $context->setTitle( $title ); + $context->setSkin( $skin ); + $outputPage = new OutputPage( $context ); + $context->setOutput( $outputPage ); + $skin->setContext( $context ); + $outputPage->setTitle( $title ); + $outputPage->output( true ); + return $skin; + } + + /** + * Converts a string to bytes + * + * @param string|int|float $size + * + * @return float|int + */ + private function getSizeInBytes( $size ) { + if ( is_string( $size ) ) { + if ( strpos( $size, 'KB' ) !== false || strpos( $size, 'kB' ) !== false ) { + $size = (float)str_replace( [ 'KB', 'kB', ' KB', ' kB' ], '', $size ); + $size = $size * 1024; + } elseif ( strpos( $size, 'B' ) !== false ) { + $size = (float)str_replace( [ ' B', 'B' ], '', $size ); + } + } + return $size; + } + + /** + * Get the list of skins and their maximum size + * + * @return array + */ + public function provideSkinsForModulesSize() { + $allowedSkins = [ 'vector-2022', 'vector' ]; + $skins = []; + foreach ( $allowedSkins as $skinName ) { + $maxSizes = $this->getMaxSize( $skinName ); + if ( empty( $maxSizes ) ) { + continue; + } + $skins[ $skinName ] = [ $skinName, $maxSizes ]; + } + return $skins; + } + + /** + * Tests the size of modules in allowed skins + * + * @param string $skinName + * @param array $maxSizes + * + * @dataProvider provideSkinsForModulesSize + * @coversNothing + * + * @return void + * @throws \Wikimedia\RequestTimeout\TimeoutException + * @throws MediaWiki\Config\ConfigException + */ + public function testTotalModulesSize( $skinName, $maxSizes ) { + $skin = $this->prepareSkin( $skinName ); + $moduleStyles = $skin->getOutput()->getModuleStyles(); + $size = 0; + foreach ( $moduleStyles as $moduleName ) { + $size += $this->getContentTransferSize( $moduleName, $skinName ); + } + $stylesMaxSize = $this->getSizeInBytes( $maxSizes[ 'styles' ] ); + $message = "Performance budget for style in skin $skinName on main article namespace has been exceeded." . + " Total size of style modules is $size bytes is greater than budget size $stylesMaxSize bytes" . + " Reduce styles loaded on page load or talk to skin maintainer before modifying the budget."; + $this->assertLessThanOrEqual( $stylesMaxSize, $size, $message ); + $modulesScripts = $skin->getOutput()->getModules(); + $size = 0; + foreach ( $modulesScripts as $moduleName ) { + $size += $this->getContentTransferSize( $moduleName, $skinName ); + } + $scriptsMaxSize = $this->getSizeInBytes( $maxSizes[ 'scripts' ] ); + $message = "Performance budget for scripts in skin $skinName on main article namespace has been exceeded." . + " Total size of script modules is $size bytes is greater than budget size $scriptsMaxSize bytes" . + " Reduce scripts loaded on page load or talk to skin maintainer before modifying the budget."; + $this->assertLessThanOrEqual( $scriptsMaxSize, $size, $message ); + } +}