resourceloader: Optimize module registry sent in the startup module
authorMarius Hoch <hoo@online.de>
Sun, 7 Jul 2013 22:51:15 +0000 (00:51 +0200)
committerMarius Hoch <hoo@online.de>
Fri, 25 Apr 2014 17:32:19 +0000 (19:32 +0200)
The optimization basically works like this:

* Given module A with the dependencies B and C and module B with the
  dependency C.
* Don't tell the client that A depends on C, as that's already included
  in module B.

This way we can reduce the amount of data for module registration sent
to the client.

The code here isn't polished yet, but it works and should be good enough
to demonstrate my idea and implementation.

Change-Id: I7732a3b1d879c5eef059e136a5241d6d48046872

includes/resourceloader/ResourceLoaderStartUpModule.php
tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php

index 005081c..0afac05 100644 (file)
@@ -117,6 +117,75 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                return $this->configVars[$hash];
        }
 
+       /**
+        * Recursively get all explicit and implicit dependencies for to the given module.
+        *
+        * @param array $registryData
+        * @param string $moduleName
+        * @return array
+        */
+       protected static function getImplicitDependencies( Array $registryData, $moduleName ) {
+               static $dependencyCache = array();
+
+               // The list of implicit dependencies won't be altered, so we can
+               // cache them without having to worry.
+               if ( !isset( $dependencyCache[$moduleName] ) ) {
+
+                       if ( !isset( $registryData[$moduleName] ) ) {
+                               // Dependencies may not exist
+                               $dependencyCache[$moduleName] = array();
+                       } else {
+                               $data = $registryData[$moduleName];
+                               $dependencyCache[$moduleName] = $data['dependencies'];
+
+                               foreach ( $data['dependencies'] as $dependency ) {
+                                       // Recursively get the dependencies of the dependencies
+                                       $dependencyCache[$moduleName] = array_merge(
+                                               $dependencyCache[$moduleName],
+                                               self::getImplicitDependencies( $registryData, $dependency )
+                                       );
+                               }
+                       }
+               }
+
+               return $dependencyCache[$moduleName];
+       }
+
+       /**
+        * Optimize the dependency tree in $this->modules and return it.
+        *
+        * The optimization basically works like this:
+        *      Given we have module A with the dependencies B and C
+        *              and module B with the dependency C.
+        *      Now we don't have to tell the client to explicitly fetch module
+        *              C as that's already included in module B.
+        *
+        * This way we can reasonably reduce the amout of module registration
+        * data send to the client.
+        *
+        * @param Array &$registryData Modules keyed by name with properties:
+        *  - string 'version'
+        *  - array 'dependencies'
+        *  - string|null 'group'
+        *  - string 'source'
+        *  - string|false 'loader'
+        */
+       public static function compileUnresolvedDependencies( Array &$registryData ) {
+               foreach ( $registryData as $name => &$data ) {
+                       if ( $data['loader'] !== false ) {
+                               continue;
+                       }
+                       $dependencies = $data['dependencies'];
+                       foreach ( $data['dependencies'] as $dependency ) {
+                               $implicitDependencies = self::getImplicitDependencies( $registryData, $dependency );
+                               $dependencies = array_diff( $dependencies, $implicitDependencies );
+                       }
+                       // Rebuild keys
+                       $data['dependencies'] = array_values( $dependencies );
+               }
+       }
+
+
        /**
         * Get registration code for all modules.
         *
@@ -157,6 +226,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        );
                }
 
+               self::compileUnresolvedDependencies( $registryData );
+
                // Register sources
                $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
 
index 5b51ef8..c4412de 100644 (file)
@@ -151,6 +151,7 @@ mw.loader.addSource( {
                                                        'test.x.foo',
                                                        'test.x.bar',
                                                        'test.x.util',
+                                                       'test.x.unknown',
                                                ),
                                        ) ),
                                        'test.group.foo.1' => new ResourceLoaderTestModule( array(
@@ -211,7 +212,6 @@ mw.loader.addSource( {
         "test.x.bar",
         "1388534400",
         [
-            "test.x.core",
             "test.x.util"
         ]
     ],
@@ -221,7 +221,7 @@ mw.loader.addSource( {
         [
             "test.x.foo",
             "test.x.bar",
-            "test.x.util"
+            "test.x.unknown"
         ]
     ],
     [
@@ -256,7 +256,10 @@ mw.loader.addSource( {
 
        /**
         * @dataProvider provideGetModuleRegistrations
+        * @covers ResourceLoaderStartupModule::optimizeDependencies
         * @covers ResourceLoaderStartUpModule::getModuleRegistrations
+        * @covers ResourceLoader::makeLoaderSourcesScript
+        * @covers ResourceLoader::makeLoaderRegisterScript
         */
        public function testGetModuleRegistrations( $case ) {
                if ( isset( $case['sources'] ) ) {