From 47d5ad564bb7e06539981068ccf3432e2e30d91e Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Mon, 14 Mar 2011 13:24:30 +0000 Subject: [PATCH] Followup r83885: implement maximum line length and statement termination (each statement on its own line) in JavaScriptMinifier. Also add globals for these things and update minify.php for these new config vars. --- includes/DefaultSettings.php | 13 ++++-- includes/libs/JavaScriptMinifier.php | 54 ++++++++++++++++++++-- includes/resourceloader/ResourceLoader.php | 6 ++- maintenance/minify.php | 14 ++++-- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 6aff1d6d86..74d1b64537 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -2505,10 +2505,17 @@ $wgResourceLoaderDebug = false; $wgResourceLoaderUseESI = false; /** - * Enable removal of some of the vertical whitespace (like \r and \n) from - * JavaScript code when minifying. + * Put each statement on its own line when minifying JavaScript. This makes + * debugging in non-debug mode a bit easier. */ -$wgResourceLoaderMinifyJSVerticalSpace = false; +$wgResourceLoaderMinifierStatementsOnOwnLine = false; + +/** + * Maximum line length when minifying JavaScript. This is not a hard maximum: + * the minifier will try not to produce lines longer than this, but may be + * forced to do so in certain cases. + */ +$wgResourceLoaderMinifierMaxLineLength = 1000; /** * Whether to include the mediawiki.legacy JS library (old wikibits.js), and its diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php index 4c7256d119..710ebe3389 100644 --- a/includes/libs/JavaScriptMinifier.php +++ b/includes/libs/JavaScriptMinifier.php @@ -66,10 +66,16 @@ class JavaScriptMinifier { /** * Returns minified JavaScript code. * + * NOTE: $maxLineLength isn't a strict maximum. Longer lines will be produced when + * literals (e.g. quoted strings) longer than $maxLineLength are encountered + * or when required to guard against semicolon insertion. + * * @param $s String JavaScript code to minify + * @param $statementsOnOwnLine Bool Whether to put each statement on its own line + * @param $maxLineLength Int Maximum length of a single line, or -1 for no maximum. * @return String Minified code */ - public static function minify( $s ) { + public static function minify( $s, $statementsOnOwnLine = false, $maxLineLength = 1000 ) { // First we declare a few tables that contain our parsing rules // $opChars : characters, which can be combined without whitespace in between them @@ -377,6 +383,23 @@ class JavaScriptMinifier { self::TYPE_LITERAL => true ) ); + + // Rules for when newlines should be inserted if + // $statementsOnOwnLine is enabled. + // $newlineBefore is checked before switching state, + // $newlineAfter is checked after + $newlineBefore = array( + self::STATEMENT => array( + self::TYPE_BRACE_CLOSE => true, + ), + ); + $newlineAfter = array( + self::STATEMENT => array( + self::TYPE_BRACE_OPEN => true, + self::TYPE_PAREN_CLOSE => true, + self::TYPE_SEMICOLON => true, + ), + ); // $divStates : Contains all states that can be followed by a division operator $divStates = array( @@ -391,6 +414,7 @@ class JavaScriptMinifier { $out = ''; $pos = 0; $length = strlen( $s ); + $lineLength = 0; $newlineFound = true; $state = self::STATEMENT; $stack = array(); @@ -492,7 +516,7 @@ class JavaScriptMinifier { } // Now get the token type from our type array - $token = substr( $s, $pos, $end - $pos ); + $token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token ) $type = isset( $tokenTypes[$token] ) ? $tokenTypes[$token] : self::TYPE_LITERAL; if( $newlineFound && isset( $semicolon[$state][$type] ) ) { @@ -500,20 +524,38 @@ class JavaScriptMinifier { // could add the ; token here ourselves, keeping the newline has a few advantages. $out .= "\n"; $state = self::STATEMENT; - } elseif( false /* Put your newline condition here */ ) { + $lineLength = 0; + } elseif( $maxLineLength > 0 && $lineLength + $end - $pos > $maxLineLength && + !isset( $semicolon[$state][$type] ) ) + { + // This line would get too long if we added $token, so add a newline first. + // Only do this if it won't trigger semicolon insertion though. $out .= "\n"; + $lineLength = 0; // Check, whether we have to separate the token from the last one with whitespace } elseif( !isset( $opChars[$last] ) && !isset( $opChars[$ch] ) ) { $out .= ' '; + $lineLength++; // Don't accidentally create ++, -- or // tokens } elseif( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) { $out .= ' '; + $lineLength++; } $out .= $token; + $lineLength += $end - $pos; // += strlen( $token ) $last = $s[$end - 1]; $pos = $end; $newlineFound = false; + + // Output a newline after the token if required + // This is checked before AND after switching state + $newlineAdded = false; + if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineBefore[$state][$type] ) ) { + $out .= "\n"; + $lineLength = 0; + $newlineAdded = true; + } // Now that we have output our token, transition into the new state. if( isset( $push[$state][$type] ) && count( $stack ) < self::STACK_LIMIT ) { @@ -524,6 +566,12 @@ class JavaScriptMinifier { } elseif( isset( $goto[$state][$type] ) ) { $state = $goto[$state][$type]; } + + // Check for newline insertion again + if ( $statementsOnOwnLine && !$newlineAdded && isset( $newlineAfter[$state][$type] ) ) { + $out .= "\n"; + $lineLength = 0; + } } return $out; } diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index e376b6a4e1..8903e5b6a6 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -121,6 +121,7 @@ class ResourceLoader { * @return String: Filtered data, or a comment containing an error message */ protected function filter( $filter, $data ) { + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; wfProfileIn( __METHOD__ ); // For empty/whitespace-only data or for unknown filters, don't perform @@ -147,7 +148,10 @@ class ResourceLoader { try { switch ( $filter ) { case 'minify-js': - $result = JavaScriptMinifier::minify( $data ); + $result = JavaScriptMinifier::minify( $data, + $wgResourceLoaderMinifierStatementsOnOwnLine, + $wgResourceLoaderMinifierMaxLineLength + ); $result .= "\n\n/* cache key: $key */\n"; break; case 'minify-css': diff --git a/maintenance/minify.php b/maintenance/minify.php index de9c53a960..c50f941b48 100644 --- a/maintenance/minify.php +++ b/maintenance/minify.php @@ -35,8 +35,11 @@ class MinifyScript extends Maintenance { "Directory for output. If this is not specified, and neither is --outfile, then the\n" . "output files will be sent to the same directories as the input files.", false, true ); - $this->addOption( 'minify-vertical-space', - "Boolean value for minifying the vertical space for javascript.", + $this->addOption( 'js-statements-on-own-line', + "Boolean value for putting statements on their own line when minifying JavaScript.", + false, true ); + $this->addOption( 'js-max-line-length', + "Maximum line length for JavaScript minification.", false, true ); $this->mDescription = "Minify a file or set of files.\n\n" . "If --outfile is not specified, then the output file names will have a .min extension\n" . @@ -99,7 +102,7 @@ class MinifyScript extends Maintenance { } public function minify( $inPath, $outPath ) { - global $wgResourceLoaderMinifyJSVerticalSpace; + global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength; $extension = $this->getExtension( $inPath ); $this->output( basename( $inPath ) . ' -> ' . basename( $outPath ) . '...' ); @@ -117,7 +120,10 @@ class MinifyScript extends Maintenance { switch ( $extension ) { case 'js': - $outText = JavaScriptDistiller::stripWhiteSpace( $inText, $this->getOption( 'minify-vertical-space', $wgResourceLoaderMinifyJSVerticalSpace ) ); + $outText = JavaScriptMinifier::minify( $inText, + $this->getOption( 'js-statements-on-own-line', $wgResourceLoaderMinifierStatementsOnOwnLine ), + $this->getOption( 'js-max-line-length', $wgResourceLoaderMinifierMaxLineLength ) + ); break; case 'css': $outText = CSSMin::minify( $inText ); -- 2.20.1