r69432 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r69431‎ | r69432 | r69433 >
Date:18:39, 16 July 2010
Author:tparscal
Status:resolved (Comments)
Tags:
Comment:
Refactored the ResourceLoader class, integrated the MessageBlobStore functionality into ResourceLoader, and moved registration for core modules (base and otherwise) to resources/Resources.php
Modified paths:
  • /branches/resourceloader/phase3/includes/ResourceLoader.php (modified) (history)
  • /branches/resourceloader/phase3/load.php (modified) (history)
  • /branches/resourceloader/phase3/resources/Resources.php (added) (history)
  • /branches/resourceloader/phase3/resources/test/index.html (modified) (history)

Diff [purge]

Index: branches/resourceloader/phase3/includes/ResourceLoader.php
@@ -15,315 +15,310 @@
1616 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1717 * http://www.gnu.org/copyleft/gpl.html
1818 *
19 - * @author Roan Kattouw
20 - *
 19+ * @author Roan Kattouw, Trevor Parscal
2120 */
2221
23 -/**
24 - * TODO: Class description
 22+/*
 23+ * Dynamic JavaScript and CSS resource loading system
 24+ *
 25+ * @example
 26+ * // Registers a module with the resource loading system
 27+ * ResourceLoader::register( 'foo', array(
 28+ * // At minimum you must have a script file
 29+ * 'script' => 'resources/foo/foo.js',
 30+ * // Optionally you can have a style file as well
 31+ * 'style' => 'resources/foo/foo.css',
 32+ * // Only needed if you are doing something fancy with your loader, otherwise one will be generated for you
 33+ * 'loader' => 'resources/foo/loader.js',
 34+ * // If you need any localized messages brought into the JavaScript environment, list the keys here
 35+ * 'messages' => array( 'foo-hello', 'foo-goodbye' ),
 36+ * // Base-only scripts are special scripts loaded in the base-package
 37+ * 'base' => false,
 38+ * // Debug-only scripts are special scripts that are only loaded when requested and while in debug mode
 39+ * 'debug' => false,
 40+ * ) );
 41+ * @example
 42+ * // Responds to a resource loading request
 43+ * ResourceLoader::respond( $wgRequest );
2544 */
2645 class ResourceLoader {
27 - /**
28 - * List of core scripts to include if the "core" module is specified - it's like a bucket
29 - */
30 - private static $coreScripts = array(
31 - 'jquery' => 'resources/core/jquery-1.4.2.js',
32 - 'mw' => 'resources/core/mw.js',
33 - 'mw.config' => 'resources/core/mw/mw.config.js',
34 - 'mw.loader' => 'resources/core/mw/mw.loader.js',
35 - 'mw.msg' => 'resources/core/mw/mw.msg.js',
36 - 'mw.util' => 'resources/core/mw/mw.util.js',
37 - );
38 - private static $debugScripts = array(
39 - 'mw.debug' => 'resources/core/mw/mw.debug.js',
40 - 'mw.log' => 'resources/core/mw/mw.log.js',
41 - );
42 - /**
43 - * List of modules.
44 - *
45 - * Format:
46 - * 'modulename' => array(
47 - * 'script' => 'resources/foo/bar.js',
48 - * 'loader' => 'resources/foo/loader.js',
49 - * 'style' => 'resources/foo/bar.css',
50 - * 'messages' => array( 'messagekey1', 'messagekey2' )
51 - * );
52 - * 'script' and 'loader' are mandatory.
53 - */
54 - public static $modules = array(
55 - 'test' => array(
56 - 'script' => 'resources/test/test.js',
57 - 'loader' => 'resources/test/loader.js',
58 - 'style' => 'resources/test/test.css',
59 - ),
60 - 'foo' => array(
61 - 'script' => 'resources/test/foo.js',
62 - 'loader' => 'resources/test/loader.js',
63 - 'style' => 'resources/test/foo.css',
64 - ),
65 - 'bar' => array(
66 - 'script' => 'resources/test/bar.js',
67 - 'loader' => 'resources/test/loader.js',
68 - 'style' => 'resources/test/bar.css',
69 - ),
70 - 'buz' => array(
71 - 'script' => 'resources/test/buz.js',
72 - 'loader' => 'resources/test/loader.js',
73 - 'style' => 'resources/test/buz.css',
74 - ),
75 - 'baz' => array(
76 - 'script' => 'resources/test/baz.js',
77 - 'loader' => 'resources/test/loader.js',
78 - 'style' => 'resources/test/baz.css',
79 - ),
80 - 'wikibits' => array(
81 - 'script' => 'skins/common/wikibits.js',
82 - 'loader' => 'skins/common/loader.js',
83 - ),
84 - );
8546
86 - private $loadedModules = array();
87 - private $includeCore = false;
 47+ /* Protected Static Members */
8848
89 - private $useJSMin = false;
90 - private $useCSSMin = true;
91 - private $useCSSJanus = true;
92 - private $useDebugMode = false;
 49+ protected static $modules = array();
9350
94 - private $lang;
 51+ /* Protected Static Methods */
9552
96 - public function __construct( $lang ) {
97 - $this->lang = $lang;
98 - }
99 -
10053 /**
101 - * Add a module to the output. This includes the module's
102 - * JS itself, its style and its messages.
103 - * @param $module string Module name
 54+ * Runs text through a filter, caching the filtered result for future calls
 55+ *
 56+ * @param string $filter name of filter to run
 57+ * @param string $data text to filter, such as JavaScript or CSS text
 58+ * @param string $file path to file being filtered, (optional: only required for CSS to resolve paths)
 59+ * @return string filtered data
10460 */
105 - public function addModule( $module ) {
106 - if ( $module == 'core' ) {
107 - $this->includeCore = true;
108 - } else if ( isset( self::$modules[$module] ) ) {
109 - $this->loadedModules[] = $module;
110 - } else {
111 - // We have a problem, they've asked for something we don't have!
112 - }
113 - }
114 -
115 - public function setUseJSMin( $use ) {
116 - $this->useJSMin = $use;
117 - }
118 -
119 - public function setUseCSSMin( $use ) {
120 - $this->useCSSMin = $use;
121 - }
122 -
123 - public function setUseCSSJanus( $use ) {
124 - $this->useCSSJanus = $use;
125 - }
126 -
127 - public function setUseDebugMode( $use ) {
128 - $this->useDebugMode = $use;
129 - }
130 -
131 - public function getOutput() {
132 - // Because these are keyed by module, in the case that more than one module asked for the same script only the
133 - // first will end up being registered - the client loader can't handle multiple modules per implementation yet,
134 - // so this is fine, but causes silent failure it strange abusive cases
135 - $this->loadedModules = array_unique( $this->loadedModules );
136 - $retval = '';
137 -
138 - if ( $this->includeCore ) {
139 - // TODO: file_get_contents() errors?
140 - // TODO: CACHING!
141 - foreach ( self::$coreScripts as $script ) {
142 - if ( file_exists( $script ) ) {
143 - $retval .= file_get_contents( $script );
144 - }
145 - }
146 - if ( $this->useDebugMode ) {
147 - // TODO: file_get_contents() errors?
148 - // TODO: CACHING!
149 - foreach ( self::$debugScripts as $script ) {
150 - if ( file_exists( $script ) ) {
151 - $retval .= file_get_contents( $script );
152 - }
153 - }
154 - }
155 - $retval .= $this->getLoaderJS();
156 - }
157 -
158 - /*
159 - * Skin::makeGlobalVariablesScript needs to be modified so that we still output the globals for now, but also
160 - * put them into the initial payload like this:
161 - *
162 - * // Sets the inital configuration
163 - * mw.config.set( { 'name': 'value', ... } );
164 - *
165 - * Also, the naming of these variables is horrible and sad, hopefully this can be worked on
166 - */
167 -
168 - // Get messages in one go
169 - $blobs = MessageBlobStore::get( $this->lang, $this->loadedModules );
170 -
171 - // TODO: file_get_contents() errors?
172 - // TODO: CACHING!
173 - foreach ( $this->loadedModules as $module ) {
174 - $mod = self::$modules[$module];
175 - $script = $style = '';
176 - $messages = isset( $blobs[$module] ) ? $blobs[$module] : '';
177 - if ( file_exists( $mod['script'] ) ) {
178 - $script = file_get_contents( $mod['script'] );
179 - }
180 - if ( !$this->useDebugMode ) {
181 - $script = preg_replace( '/\n\s*mw\.log\(([^\)]*\))*\s*[\;\n]/U', "\n", $script );
182 - }
183 - if ( isset( $mod['style'] ) && file_exists( $mod['style'] ) ) {
184 - $css = file_get_contents( $mod['style'] );
185 - if ( $this->useCSSJanus ) {
186 - $css = $this->cssJanus( $css );
187 - }
188 - if ( $this->useCSSMin ) {
189 - $css = $this->cssMin( $css, $mod['style'] );
190 - }
191 - $style = Xml::escapeJsString( $css );
192 - }
193 -
194 - $retval .= "mw.loader.implement( '$module', function() { $script }, '$style', { $messages } );\n";
195 - }
196 -
197 - if ( !$this->useDebugMode && $this->useJSMin ) {
198 - $retval = $this->jsMin( $retval );
199 - }
200 - return $retval;
201 - }
202 -
203 - public function getLoaderJS() {
204 - $retval = '';
205 - // Only add each file once (just in case there are multiple modules in a single loader, which is common)
206 - $loaders = array();
207 - foreach ( self::$modules as $name => $module ) {
208 - // TODO: file_get_contents() errors?
209 - // TODO: CACHING!
210 - if ( !in_array( $module['loader'], $loaders ) && file_exists( $module['loader'] ) ) {
211 - $retval .= file_get_contents( $module['loader'] );
212 - $loaders[] = $module['loader'];
213 - }
214 - }
215 - // FIXME: Duplicated; centralize in doJSTransforms() or something?
216 - if ( $this->useJSMin ) {
217 - $retval = $this->jsMin( $retval );
218 - }
219 - return $retval;
220 - }
221 -
222 - public function jsMin( $js ) {
 61+ protected static function filter( $filter, $data, $file = null ) {
22362 global $wgMemc;
224 - $key = wfMemcKey( 'resourceloader', 'jsmin', md5( $js ) );
 63+ $key = wfMemcKey( 'resourceloader', $filter, md5( $data ) );
22564 $cached = $wgMemc->get( $key );
22665 if ( $cached !== false && $cached !== null ) {
22766 return $cached;
22867 }
229 - $retval = JSMin::minify( $js );
230 - $wgMemc->set( $key, $retval );
231 - return $retval;
232 - }
233 -
234 - public function cssMin( $css, $file ) {
235 - global $wgMemc;
236 - $key = wfMemcKey( 'resourceloader', 'cssmin', md5( $css ) );
237 - $cached = $wgMemc->get( $key );
238 - if( $cached !== false && $cached !== null ) {
239 - return $cached;
 68+ switch ( $filter ) {
 69+ case 'minify-js':
 70+ $result = JSMin::minify( $data );
 71+ break;
 72+ case 'minify-css':
 73+ $result = Minify_CSS::minify( $data, array( 'currentDir' => dirname( $file ), 'docRoot' => '.' ) );
 74+ break;
 75+ case 'flip-css':
 76+ $cssJanus = new CSSJanus();
 77+ $result = $cssJanus::transform( $data, true, false );
 78+ break;
 79+ case 'strip-debug':
 80+ $result = preg_replace( '/\n\s*mw\.log\(([^\)]*\))*\s*[\;\n]/U', "\n", $data );
 81+ break;
 82+ default:
 83+ // Don't cache anything, just pass right through
 84+ return $data;
24085 }
241 - // TODO: Test how well this path rewriting stuff works with various setups
242 - $retval = Minify_CSS::minify( $css, array( 'currentDir' => dirname( $file ), 'docRoot' => '.' ) );
243 - $wgMemc->set( $key, $retval );
244 - return $retval;
 86+ $wgMemc->set( $key, $result );
 87+ return $result;
24588 }
246 -
247 - public function cssJanus( $css ) {
248 - global $wgMemc;
249 - $key = wfMemcKey( 'resourceloader', 'cssjanus', md5( $css ) );
250 - $cached = $wgMemc->get( $key );
251 - if ( $cached !== false && $cached !== null ) {
252 - return $cached;
253 - }
254 - $retval = $css; // TODO: Actually flip
255 - $wgMemc->set( $key, $retval );
256 - return $retval;
257 - }
258 -}
259 -
260 -class MessageBlobStore {
26189 /**
262 - * Get the message blobs for a set of modules
263 - * @param $lang string Language code
264 - * @param $modules array Array of module names
265 - * @return array An array of incomplete JSON objects (i.e. without the {} ) containing messages keys and their values. Array keys are module names.
 90+ * Get a list of JSON encoded message lists for a set of modules, automatically caching JSON blobs in the database
 91+ *
 92+ * @param string $lang language code
 93+ * @param array $modules module names
 94+ * @return array JSON objects containing message keys and values for each module, keyed by module name
26695 */
267 - public static function get( $lang, $modules ) {
 96+ protected static function messages( $lang, $modules ) {
26897 // TODO: Invalidate blob when module touched
269 - if ( !count( $modules ) ) {
 98+ if ( empty( $modules ) ) {
27099 return array();
271100 }
272101 // Try getting from the DB first
273 - $blobs = self::getFromDB( $lang, $modules );
274 -
 102+ $blobs = array();
 103+ $dbr = wfGetDB( DB_SLAVE );
 104+ $result = $dbr->select(
 105+ 'msg_resource',
 106+ array( 'mr_blob', 'mr_resource' ),
 107+ array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
 108+ __METHOD__
 109+ );
 110+ foreach ( $result as $row ) {
 111+ $blobs[$row->mr_resource] = $row->mr_blob;
 112+ }
275113 // Generate blobs for any missing modules and store them in the DB
276114 $missing = array_diff( $modules, array_keys( $blobs ) );
277115 foreach ( $missing as $module ) {
278 - $blob = self::generateMessageBlob( $lang, $module );
279 - if ( $blob ) {
280 - self::set( $lang, $module, $blob );
 116+ // Build message blob for module messages
 117+ $messages = isset( static::$modules[$module]['messages'] ) ?
 118+ array_keys( static::$modules[$module]['messages'] ) : false;
 119+ if ( $messages ) {
 120+ foreach ( ResourceLoader::$modules[$module]['messages'] as $key ) {
 121+ $messages[$encKey] = wfMsgExt( $key, array( 'language' => $lang ) );
 122+ }
 123+ $blob = json_encode( $messages );
 124+ // Store message blob
 125+ $dbw = wfGetDb( DB_MASTER );
 126+ $dbw->replace(
 127+ 'msg_resource',
 128+ array( array( 'mr_lang', 'mr_resource' ) ),
 129+ array( array(
 130+ 'mr_lang' => $lang,
 131+ 'mr_module' => $module,
 132+ 'mr_blob' => $blob,
 133+ 'mr_timestamp' => wfTimestampNow(),
 134+ ) )
 135+ );
 136+ // Add message blob to result
281137 $blobs[$module] = $blob;
282138 }
283139 }
284140 return $blobs;
285141 }
286142
 143+ /* Static Methods */
 144+
287145 /**
288 - * Set the message blob for a given module in a given language
289 - * @param $lang string Language code
290 - * @param $module string Module name
291 - * @param $blob string Incomplete JSON object, see get()
 146+ * Registers a module with the ResourceLoader system
 147+ *
 148+ * @param mixed $module string of name of module or array of name/options pairs
 149+ * @param array $options module options (optional when using multiple-registration calling style)
 150+ * @return boolean false if there were any errors, in which case one or more modules were not registered
 151+ *
 152+ * $options format:
 153+ * array(
 154+ * 'script' => [string: path to file],
 155+ * 'style' => [string: path to file, optional],
 156+ * 'loader' => [string: path to file, optional],
 157+ * 'messages' => [array: message keys, optional],
 158+ * 'base' => [boolean: include in base package only, optional],
 159+ * 'debug' => [boolean: include in debug mode only, optional],
 160+ * )
 161+ *
 162+ * @todo We need much more clever error reporting, not just in detailing what happened, but in bringing errors to
 163+ * the client in a way that they can easily see them if they want to, such as by using FireBug
292164 */
293 - public static function set( $lang, $module, $blob ) {
294 - $dbw = wfGetDb( DB_MASTER );
295 - $dbw->replace( 'msg_resource', array( array( 'mr_lang', 'mr_resource' ) ),
296 - array( array(
297 - 'mr_lang' => $lang,
298 - 'mr_module' => $module,
299 - 'mr_blob' => $blob,
300 - 'mr_timestamp' => wfTimestampNow(),
301 - ) )
302 - );
303 - }
304 -
305 - private static function getFromDB( $lang, $modules ) {
306 - $retval = array();
307 - $dbr = wfGetDB( DB_SLAVE );
308 - $res = $dbr->select( 'msg_resource', array( 'mr_blob', 'mr_resource' ),
309 - array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
310 - __METHOD__
311 - );
312 - foreach ( $res as $row ) {
313 - $retval[$row->mr_resource] = $row->mr_blob;
 165+ public static function register( $module, $options = array() ) {
 166+ // Allow multiple modules to be registered in one call
 167+ if ( is_array( $module ) && empty( $options ) ) {
 168+ $success = true;
 169+ foreach ( $module as $name => $options ) {
 170+ if ( !static::register( $name, $options ) ) {
 171+ $success = false;
 172+ }
 173+ }
 174+ return $success;
314175 }
315 - return $retval;
316 - }
317 -
318 - private static function generateMessageBlob( $lang, $module ) {
319 - if ( !isset ( ResourceLoader::$modules[$module]['messages'] ) ) {
 176+ // Disallow duplicate registrations
 177+ if ( isset( static::$modules[$module] ) ) {
 178+ // A module has already been registered by this name
320179 return false;
321180 }
322 - $messages = array();
323 - foreach ( ResourceLoader::$modules[$module]['messages'] as $key ) {
324 - $encKey = Xml::escapeJsString( $key );
325 - $encValue = Xml::escapeJsString( wfMsg( $key ) ); // TODO: Use something rawer than wfMsg()?
326 - $messages[] = "'$encKey': '$encValue'";
 181+ // Always include a set of default options in each registration - more data, less isset() checks
 182+ $options = array_merge( array(
 183+ 'script' => null,
 184+ 'style' => null,
 185+ 'messages' => null,
 186+ 'loader' => null,
 187+ 'base' => false,
 188+ 'debug' => false,
 189+ ), $options );
 190+ // Validate script option
 191+ if ( !is_string( $options['script'] ) ) {
 192+ // Module does not include a script
 193+ return false;
327194 }
328 - return implode( ",", $messages );
 195+ if ( !file_exists( $options['script'] ) ) {
 196+ // Script file does not exist
 197+ return false;
 198+ }
 199+ if ( $options['loader'] !== null && !file_exists( $options['loader'] ) ) {
 200+ // Loader file does not exist
 201+ return false;
 202+ }
 203+ if ( $options['style'] !== null && !file_exists( $options['style'] ) ) {
 204+ // Style file does not exist
 205+ return false;
 206+ }
 207+ static::$modules[$module] = $options;
329208 }
 209+ /*
 210+ * Outputs a response to a resource load-request, including a content-type header
 211+ *
 212+ * @param array $modules module names to include in the request
 213+ * @param array $options options which affect the content of the response (optional)
 214+ *
 215+ * $options format:
 216+ * array(
 217+ * 'user' => [boolean: true for logged in, false for anon, optional, state of current user by default],
 218+ * 'lang' => [string: language code, optional, code of default language by default],
 219+ * 'skin' => [string: name of skin, optional, name of default skin by default],
 220+ * 'dir' => [string: 'ltr' or 'rtl', optional, direction of lang by default],
 221+ * 'base' => [boolean: true to include base-only scripts, optional, false by default],
 222+ * 'debug' => [boolean: true to include debug-only scripts, optional, false by default],
 223+ * )
 224+ */
 225+ public static function respond( WebRequest $request ) {
 226+ global $wgUser, $wgLang, $wgDefaultSkin;
 227+ // Fallback on system settings
 228+ $parameters = array_merge( array(
 229+ 'user' => $request->getBool( 'user', $wgUser->isLoggedIn() ),
 230+ 'lang' => $request->getVal( 'lang', $wgLang->getCode() ),
 231+ 'skin' => $request->getVal( 'skin', $wgDefaultSkin ),
 232+ 'base' => $request->getBool( 'base' ),
 233+ 'debug' => $request->getBool( 'debug' ),
 234+ ) );
 235+ $modules = explode( '|', $request->getVal( 'modules' ) );
 236+ // Get the direction from the requested language
 237+ if ( !isset( $parameters['dir'] ) ) {
 238+ $lang = $wgLang->factory( $parameters['lang'] );
 239+ $parameters['dir'] = $lang->getDir();
 240+ }
 241+ // Optionally include all base-only scripts
 242+ $base = array();
 243+ if ( $parameters['base'] ) {
 244+ foreach ( static::$modules as $name => $options ) {
 245+ if ( $options['base'] ) {
 246+ // Only include debug scripts in debug mode
 247+ if ( $options['debug'] ) {
 248+ if ( $parameters['debug'] ) {
 249+ $base[] = $name;
 250+ }
 251+ } else {
 252+ $base[] = $name;
 253+ }
 254+ }
 255+ }
 256+ }
 257+ // Include requested modules which have been registered - ignoring any which have not been
 258+ $other = array();
 259+ foreach ( static::$modules as $name => $options ) {
 260+ if ( in_array( $name, $modules ) && !in_array( $name, $base )) {
 261+ $other[] = $name;
 262+ }
 263+ }
 264+ // Use output buffering
 265+ ob_start();
 266+ // Optionally include base modules
 267+ if ( $parameters['base'] ) {
 268+ // Base modules
 269+ foreach ( $base as $module ) {
 270+ readfile( static::$modules[$module]['script'] );
 271+ }
 272+ // All module loaders - keep track of which loaders have been included to prevent multiple modules with a
 273+ // single loader causing the loader to be included more than once
 274+ $loaders = array();
 275+ foreach ( self::$modules as $name => $options ) {
 276+ if ( $options['loader'] !== null && !in_array( $options['loader'], $loaders ) ) {
 277+ readfile( $options['loader'] );
 278+ $loaders[] = $options['loader'];
 279+ }
 280+ }
 281+ }
 282+
 283+ /*
 284+ * Skin::makeGlobalVariablesScript needs to be modified so that we still output the globals for now, but also
 285+ * put them into the initial payload like this:
 286+ *
 287+ * // Sets the inital configuration
 288+ * mw.config.set( { 'name': 'value', ... } );
 289+ *
 290+ * Also, the naming of these variables is horrible and sad, hopefully this can be worked on
 291+ */
 292+
 293+ // Other modules
 294+ $blobs = static::messages( $parameters['lang'], $other );
 295+ foreach ( $other as $module ) {
 296+ // Script
 297+ $script = file_get_contents( static::$modules[$module]['script'] );
 298+ if ( !$parameters['debug'] ) {
 299+ $script = static::filter( 'strip-debug', $script );
 300+ }
 301+ // Style
 302+ $style = static::$modules[$module]['style'] ? file_get_contents( static::$modules[$module]['style'] ) : '';
 303+ if ( $style !== '' ) {
 304+ if ( $parameters['dir'] == 'rtl' ) {
 305+ $style = static::filter( 'flip-css', $style );
 306+ }
 307+ $style = Xml::escapeJsString(
 308+ static::filter( 'minify-css', $style, static::$modules[$module]['style'] )
 309+ );
 310+ }
 311+ // Messages
 312+ $messages = isset( $blobs[$module] ) ? $blobs[$module] : '{}';
 313+ // Output
 314+ echo "mw.loader.implement( '{$module}', function() { {$script} }, '{$style}', {$messages} );\n";
 315+ }
 316+ // Set headers -- when we support CSS only mode, this might change!
 317+ header( 'Content-Type: text/javascript' );
 318+ // Final processing
 319+ if ( $parameters['debug'] ) {
 320+ ob_end_flush();
 321+ } else {
 322+ echo static::filter( 'minify-js', ob_get_clean() );
 323+ }
 324+ }
330325 }
\ No newline at end of file
Index: branches/resourceloader/phase3/load.php
@@ -15,7 +15,7 @@
1616 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1717 * http://www.gnu.org/copyleft/gpl.html
1818 *
19 - * @author Roan Kattouw
 19+ * @author Roan Kattouw, Trevor Parscal
2020 *
2121 */
2222
@@ -45,23 +45,11 @@
4646 // FIXME: Doesn't this execute the rest of the request anyway?
4747 // Was taken from api.php so I guess it's maybe OK but it doesn't look good.
4848 }
 49+// Include core resource list
 50+require_once "$IP/resources/Resources.php";
 51+// Respond to resource loading request
 52+ResourceLoader::respond( $wgRequest );
4953
50 -$loader = new ResourceLoader( $wgRequest->getVal( 'lang', 'en' ) );
51 -$loader->setUseJSMin( $wgRequest->getBool( 'jsmin', true ) );
52 -$loader->setUseCSSMin( $wgRequest->getBool( 'cssmin', true ) );
53 -$loader->setUseCSSJanus( $wgRequest->getVal( 'dir', 'ltr' ) == 'rtl' );
54 -$loader->setUseDebugMode( $wgRequest->getBool( 'debug', false ) );
55 -$moduleParam = $wgRequest->getVal( 'modules' );
56 -$modules = $moduleParam ? explode( '|', $moduleParam ) : array();
57 -foreach ( $modules as $module ) {
58 - $loader->addModule( $module );
59 -}
60 -
61 -// TODO: Cache-Control header
62 -// TODO: gziphandler
63 -header( 'Content-Type', 'text/javascript' );
64 -echo $loader->getOutput();
65 -
6654 wfProfileOut( 'loader.php' );
6755 wfLogProfilingData();
6856
Index: branches/resourceloader/phase3/resources/test/index.html
@@ -8,7 +8,7 @@
99 color: white;
1010 }
1111 </style>
12 - <script type="text/javascript" src="../../load.php?modules=core|test&debug=0"></script>
 12+ <script type="text/javascript" src="../../load.php?modules=test&base=1&debug=0"></script>
1313 <script>
1414 mw.config.set( 'wgScriptPath', '../..' );
1515 </script>
Index: branches/resourceloader/phase3/resources/Resources.php
@@ -0,0 +1,67 @@
 2+<?php
 3+
 4+ResourceLoader::register( array(
 5+ 'jquery' => array(
 6+ 'script' => 'resources/base/jquery-1.4.2.js',
 7+ 'base' => true,
 8+ ),
 9+ 'mw' => array(
 10+ 'script' => 'resources/base/mw.js',
 11+ 'base' => true,
 12+ ),
 13+ 'mw.config' => array(
 14+ 'script' => 'resources/base/mw/mw.config.js',
 15+ 'base' => true,
 16+ ),
 17+ 'mw.loader' => array(
 18+ 'script' => 'resources/base/mw/mw.loader.js',
 19+ 'base' => true,
 20+ ),
 21+ 'mw.msg' => array(
 22+ 'script' => 'resources/base/mw/mw.msg.js',
 23+ 'base' => true,
 24+ ),
 25+ 'mw.util' => array(
 26+ 'script' => 'resources/base/mw/mw.util.js',
 27+ 'base' => true,
 28+ ),
 29+ 'mw.debug' => array(
 30+ 'script' => 'resources/base/mw/mw.debug.js',
 31+ 'base' => true,
 32+ 'debug' => true
 33+ ),
 34+ 'mw.log' => array(
 35+ 'script' => 'resources/base/mw/mw.log.js',
 36+ 'base' => true,
 37+ 'debug' => true
 38+ ),
 39+ 'test' => array(
 40+ 'script' => 'resources/test/test.js',
 41+ 'loader' => 'resources/test/loader.js',
 42+ 'style' => 'resources/test/test.css',
 43+ ),
 44+ 'foo' => array(
 45+ 'script' => 'resources/test/foo.js',
 46+ 'loader' => 'resources/test/loader.js',
 47+ 'style' => 'resources/test/foo.css',
 48+ ),
 49+ 'bar' => array(
 50+ 'script' => 'resources/test/bar.js',
 51+ 'loader' => 'resources/test/loader.js',
 52+ 'style' => 'resources/test/bar.css',
 53+ ),
 54+ 'buz' => array(
 55+ 'script' => 'resources/test/buz.js',
 56+ 'loader' => 'resources/test/loader.js',
 57+ 'style' => 'resources/test/buz.css',
 58+ ),
 59+ 'baz' => array(
 60+ 'script' => 'resources/test/baz.js',
 61+ 'loader' => 'resources/test/loader.js',
 62+ 'style' => 'resources/test/baz.css',
 63+ ),
 64+ 'wikibits' => array(
 65+ 'script' => 'skins/common/wikibits.js',
 66+ 'loader' => 'skins/common/loader.js',
 67+ ),
 68+) );
\ No newline at end of file
Property changes on: branches/resourceloader/phase3/resources/Resources.php
___________________________________________________________________
Added: svn:eol-style
169 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r69563Resolves remaining issues noted in CR comments on r69432.tparscal19:41, 19 July 2010

Comments

#Comment by Catrope (talk | contribs)   18:06, 19 July 2010
+ * @author Roan Kattouw, Trevor Parscal

Doxygen handles multiple authorship simply with multiple @author statements.

+				$cssJanus = new CSSJanus();
+				$result = $cssJanus::transform( $data, true, false );

WTH? Just use CSSJanus::transform().

+			case 'strip-debug':
+				$result = preg_replace( '/\n\s*mw\.log\(([^\)]*\))*\s*[\;\n]/U', "\n", $data );
+				break;

I've expressed my reservations with this in an earlier CR.

+				foreach ( ResourceLoader::$modules[$module]['messages'] as $key ) {

Now that this has been moved to the ResourceLoader class, it should use self::.

+				if ( !static::register( $name, $options ) ) {

This breaks in PHP < 5.3 , use self::. More such usages elsewhere in this file.

+	 * @param array $modules module names to include in the request
+	 * @param array $options options which affect the content of the response (optional)
[...]
+	public static function respond( WebRequest $request ) {

Documentation doesn't match module signature+implementation. I really, really, prefer the documented form over the implemented form because I know what a pain WebRequest-reliant functions are from first-hand experience.

+		$parameters = array_merge( array(
+			'user' => $request->getBool( 'user', $wgUser->isLoggedIn() ),
+			'lang' => $request->getVal( 'lang', $wgLang->getCode() ),
+			'skin' => $request->getVal( 'skin', $wgDefaultSkin ),
+			'base' => $request->getBool( 'base' ),
+			'debug' => $request->getBool( 'debug' ),
+		) );

Calling array_merge() with one argument isn't very useful.

				if ( $options['base'] ) {
+					// Only include debug scripts in debug mode
+					if ( $options['debug'] ) {
+						if ( $parameters['debug'] ) {
+							$base[] = $name;
+						}
+					} else {
+						$base[] = $name;
+					}
+				}

Can be shortened to if ( $options['base'] && ( !$options['debug'] || $parameters['debug'] ) ) { $base[] = $name; }.

+		// Include requested modules which have been registered - ignoring any which have not been
+		$other = array();
+		foreach ( static::$modules as $name => $options ) {
+			if ( in_array( $name, $modules ) && !in_array( $name, $base )) {
+				$other[] = $name;
+			}
+		}

Misses check for non-base debug modules, which are not ruled out by the documentation.

+		if ( $parameters['base'] ) {
[...]
+			// All module loaders - keep track of which loaders have been included to prevent multiple modules with a
+			// single loader causing the loader to be included more than once
+			$loaders = array();
+			foreach ( self::$modules as $name => $options ) {
[include loaders]

So the base parameter triggers the inclusion of all loaders. This makes sense to me personally, but is not documented.

+				$style = Xml::escapeJsString(
+					static::filter( 'minify-css', $style, static::$modules[$module]['style'] )
+				);

You're minifying CSS unconditionally, even in debug mode, whereas you are taking care not to minify JS in debug mode.


+			$messages = isset( $blobs[$module] ) ? $blobs[$module] : '{}';
+			// Output
+			echo "mw.loader.implement( '{$module}', function() { {$script} }, '{$style}', {$messages} );\n";

This produces {{}} if $blobs[$module] is not set. : '{}' should be : ''.

#Comment by Catrope (talk | contribs)   20:13, 19 July 2010

r69563 fixes most of these. The ones left over are:

  • Weird static call on an object to CSSJanus::transform()
  • {{}} output if blob not set
#Comment by Tim Starling (talk | contribs)   02:58, 17 September 2010

Why did you use an output buffer to accumulate the output string instead of a variable? The code would have been almost identical, just $s.=... instead of echo ..., but variables don't screw up error reporting like output buffers do.

#Comment by Catrope (talk | contribs)   11:09, 17 September 2010

This was originally done so we could use some function (forgot the name) that reads a file and writes it straight to the output, but since we process all file contents now we no longer do that. Accumulating output in a variable would also allow us to clear any error reporting from the output before writing it out: we've had quite a few bugs where the layout was messed up because a PHP warning appeared at the top of a CSS file and made the entire thing invalid CSS. Filed bug 25200 for myself.

#Comment by Trevor Parscal (WMF) (talk | contribs)   16:48, 17 September 2010

I agree that this happened because we were using readfile() a few places, but now everything is abstracted a few layers away, it makes much less sense.

#Comment by Trevor Parscal (WMF) (talk | contribs)   18:11, 12 October 2010

Output buffering is not used anymore.

Status & tagging log