MDL-40995 simplify minify integration and fix all known issues

This commit is contained in:
Petr Škoda 2013-08-01 23:20:24 +02:00
parent 7c3943ad0c
commit 6b32d6bc93
11 changed files with 316 additions and 160 deletions

149
lib/classes/minify.php Normal file
View File

@ -0,0 +1,149 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* JS and CSS compression.
*
* @package core
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Collection of JS and CSS compression methods.
*/
class core_minify {
/**
* Minify JS code.
*
* @param string $content
* @return string minified JS code
*/
public static function js($content) {
global $CFG;
require_once("$CFG->libdir/minify/lib/JSMinPlus.php");
try {
ob_start(); // JSMinPlus just echos errors, weird...
$compressed = JSMinPlus::minify($content);
if ($compressed !== false) {
ob_end_clean();
return $compressed;
}
$error = ob_get_clean();
} catch (Exception $e) {
ob_end_clean();
$error = $e->getMessage();
}
$return = <<<EOD
try {console.log('Error: Minimisation of JavaScript failed!');} catch (e) {}
// Error: $error
// Problem detected during JavaScript minimisation, please review the following code
// =================================================================================
EOD;
return $return.$content;
}
/**
* Minify JS files.
*
* @param array $files
* @return string minified JS code
*/
public static function js_files(array $files) {
if (empty($files)) {
return '';
}
$compressed = array();
foreach ($files as $file) {
$content = file_get_contents($file);
if ($content === false) {
$compressed[] = "\n\n// Cannot read JS file ".basename(dirname(dirname($file))).'/'.basename(dirname($file)).'/'.basename($file)."\n\n";
continue;
}
$compressed[] = self::js($content);
}
return implode("\n", $compressed);
}
/**
* Minify CSS code.
*
* @param string $content
* @return string minified CSS
*/
public static function css($content) {
global $CFG;
require_once("$CFG->libdir/minify/lib/Minify/CSS/Compressor.php");
$error = 'unknown';
try {
$compressed = Minify_CSS_Compressor::process($content);
if ($compressed !== false) {
return $compressed;
}
} catch (Exception $e) {
$error = $e->getMessage();
}
$return = <<<EOD
/* Error: $error */
/* Problem detected during CSS minimisation, please review the following code */
/* ========================================================================== */
EOD;
return $return.$content;
}
/**
* Minify CSS files.
*
* @param array $files
* @return string minified CSS code
*/
public static function css_files(array $files) {
if (empty($files)) {
return '';
}
$compressed = array();
foreach ($files as $file) {
$content = file_get_contents($file);
if ($content === false) {
$compressed[] = "\n\n/* Cannot read CSS file ".basename(dirname(dirname($file))).'/'.basename(dirname($file)).'/'.basename($file)."*/\n\n";
continue;
}
$compressed[] = self::css($content);
}
return implode("\n", $compressed);
}
}

View File

@ -25,7 +25,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
defined('MOODLE_INTERNAL') || die();
/**
* Stores CSS in a file at the given path.
@ -43,6 +43,11 @@
function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk = false, $chunkurl = null) {
global $CFG;
$css = '';
foreach ($cssfiles as $file) {
$css .= file_get_contents($file)."\n";
}
// Check if both the CSS optimiser is enabled and the theme supports it.
if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) {
// This is an experimental feature introduced in Moodle 2.3
@ -51,10 +56,6 @@ function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk =
// the CSS before it is cached removing excess styles and rules and stripping
// out any extraneous content such as comments and empty rules.
$optimiser = new css_optimiser;
$css = '';
foreach ($cssfiles as $file) {
$css .= file_get_contents($file)."\n";
}
$css = $theme->post_process($css);
$css = $optimiser->process($css);
@ -73,7 +74,8 @@ function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk =
// However it has the distinct disadvantage of having to minify the CSS
// before running the post process functions. Potentially things may break
// here if theme designers try to push things with CSS post processing.
$css = $theme->post_process(css_minify_css($cssfiles));
$css = $theme->post_process($css);
$css = core_minify::css($css);
}
clearstatcache();
@ -294,80 +296,6 @@ function css_send_css_not_found() {
die('CSS was not found, sorry.');
}
/**
* Uses the minify library to compress CSS.
*
* This is used if $CFG->enablecssoptimiser has been turned off. This was
* the original CSS optimisation library.
* It removes whitespace and shrinks things but does no apparent optimisation.
* Note the minify library is still being used for JavaScript.
*
* @param array $files An array of files to minify
* @return string The minified CSS
*/
function css_minify_css($files) {
global $CFG;
if (empty($files)) {
return '';
}
// We do not really want any 304 here!
// There does not seem to be any better way to prevent them here.
unset($_SERVER['HTTP_IF_NONE_MATCH']);
unset($_SERVER['HTTP_IF_MODIFIED_SINCE']);
require_once("$CFG->libdir/minify/lib/Minify/Loader.php");
Minify_Loader::register();
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // IIS may need help
}
// disable all caching, we do it in moodle
Minify::setCache(null, false);
$options = array(
// JSMin is not GNU GPL compatible, use the plus version instead.
'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
'bubbleCssImports' => false,
// Don't gzip content we just want text for storage
'encodeOutput' => false,
// Maximum age to cache, not used but required
'maxAge' => (60*60*24*20),
// The files to minify
'files' => $files,
// Turn orr URI rewriting
'rewriteCssUris' => false,
// This returns the CSS rather than echoing it for display
'quiet' => true
);
$error = 'unknown';
try {
$result = Minify::serve('Files', $options);
if ($result['success'] and $result['statusCode'] == 200) {
return $result['content'];
}
} catch (Exception $e) {
$error = $e->getMessage();
$error = str_replace("\r", ' ', $error);
$error = str_replace("\n", ' ', $error);
}
// minification failed - try to inform the theme developer and include the non-minified version
$css = <<<EOD
/* Error: $error */
/* Problem detected during theme CSS minimisation, please review the following code */
/* ================================================================================ */
EOD;
foreach ($files as $cssfile) {
$css .= file_get_contents($cssfile)."\n";
}
return $css;
}
/**
* Determines if the given value is a valid CSS colour.
*

View File

@ -30,6 +30,32 @@
defined('MOODLE_INTERNAL') || die();
/**
* Minify JavaScript files.
*
* @deprecated since 2.6
*
* @param array $files
* @return string
*/
function js_minify($files) {
debugging('js_minify() is deprecated, use core_minify::js_files() or core_minify::js() instead.');
return core_minify::js_files($files);
}
/**
* Minify CSS files.
*
* @deprecated since 2.6
*
* @param array $files
* @return string
*/
function css_minify_css($files) {
debugging('css_minify_css() is deprecated, use core_minify::css_files() or core_minify::css() instead.');
return core_minify::css_files($files);
}
/**
* List all core subsystems and their location
*

View File

@ -83,7 +83,7 @@ if ($mimetype === 'application/x-javascript' && $allowcache) {
// If it doesn't exist, minify it and save to that location.
if (!file_exists($cachefile)) {
$content = js_minify(array($file));
$content = core_minify::js_files(array($file));
js_write_cache_file_content($cachefile, $content);
}

View File

@ -30,6 +30,7 @@ define('NO_DEBUG_DISPLAY', true);
define('ABORT_AFTER_CONFIG', true);
require('../config.php'); // this stops immediately at the beginning of lib/setup.php
require_once("$CFG->dirroot/lib/jslib.php");
require_once("$CFG->dirroot/lib/classes/minify.php");
if ($slashargument = min_get_slash_argument()) {
$slashargument = ltrim($slashargument, '/');
@ -90,7 +91,7 @@ if ($rev > 0 and $rev < (time() + 60*60)) {
js_send_cached($candidate, $etag);
} else {
js_write_cache_file_content($candidate, js_minify($jsfiles));
js_write_cache_file_content($candidate, core_minify::js_files($jsfiles));
// verify nothing failed in cache file creation
clearstatcache();
if (file_exists($candidate)) {

View File

@ -23,7 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
//NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
defined('MOODLE_INTERNAL') || die();
/**
* Send javascript file content with as much caching as possible
@ -93,76 +93,6 @@ function js_send_unmodified($lastmodified, $etag) {
die;
}
/**
* Minify javascript files
* @param array $files
* @return string
*/
function js_minify($files) {
global $CFG;
if (empty($files)) {
return '';
}
require_once("$CFG->libdir/minify/lib/Minify/Loader.php");
Minify_Loader::register();
// We do not really want any 304 here!
// There does not seem to be any better way to prevent them here.
unset($_SERVER['HTTP_IF_NONE_MATCH']);
unset($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if (0 === stripos(PHP_OS, 'win')) {
Minify::setDocRoot(); // IIS may need help
}
// disable all caching, we do it in moodle
Minify::setCache(null, false);
$options = array(
// JSMin is not GNU GPL compatible, use the plus version instead.
'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
'bubbleCssImports' => false,
// Don't gzip content we just want text for storage
'encodeOutput' => false,
// Maximum age to cache, not used but required
'maxAge' => 1800,
// The files to minify
'files' => $files,
// Turn orr URI rewriting
'rewriteCssUris' => false,
// This returns the CSS rather than echoing it for display
'quiet' => true
);
$error = 'unknown';
try {
$result = Minify::serve('Files', $options);
if ($result['success'] and $result['statusCode'] == 200) {
return $result['content'];
}
} catch (Exception $e) {
$error = $e->getMessage();
$error = str_replace("\r", ' ', $error);
$error = str_replace("\n", ' ', $error);
}
// minification failed - try to inform the theme developer and include the non-minified version
$js = <<<EOD
try {console.log('Error: Minimisation of javascript failed!');} catch (e) {}
// Error: $error
// Problem detected during javascript minimisation, please review the following code
// =================================================================================
EOD;
foreach ($files as $jsfile) {
$js .= file_get_contents($jsfile)."\n";
}
return $js;
}
/**
* Create cache file for JS content
* @param string $file full file path to cache file

View File

@ -1,11 +1,9 @@
Description of Minify 2.1.7 import into Moodle
Notes:
* Do not use things within minify/lib/*
Usage:
* js_minify() from /lib/jslib.php
* css_minify_css() from /lib/csslib.php
* Do not use anything from /lib/minify/ directly, always use core_minify::*() methods.
* In 2.7dev we will import only the minimal number of files required by new core_minify class
and delete deprecated js_minify() and css_minify_css().
Changes:
* Removed index.php - Is an unused entry point program and could potentially

View File

@ -1571,7 +1571,7 @@ class YUI_config {
$configfn = $cache->get($keyname);
if ($configfn === false) {
require_once($CFG->libdir . '/jslib.php');
$configfn = js_minify($fullpath);
$configfn = core_minify::js_files(array($fullpath));
$cache->set($keyname, $configfn);
}
}

122
lib/tests/minify_test.php Normal file
View File

@ -0,0 +1,122 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* core_minify related tests.
*
* @package core
* @category phpunit
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class core_minify_testcase.
*/
class core_minify_testcase extends advanced_testcase {
public function test_css() {
$css = "
body {
background: #fff;
margin: 0;
padding: 0;
color: #281f18;
}";
$this->assertSame("body{background:#fff;margin:0;padding:0;color:#281f18}", core_minify::css($css));
}
public function test_css_files() {
global $CFG;
$testfile1 = "$CFG->tempdir/test1.css";
$testfile2 = "$CFG->tempdir/test2.css";
$testfile3 = "$CFG->tempdir/test3.css";
$css1 = "
body {
background: #fff;
margin: 0;
padding: 0;
color: #281f18;
}";
$css2 = "body{}";
file_put_contents($testfile1, $css1);
file_put_contents($testfile2, $css2);
$files = array($testfile1, $testfile2);
$this->assertSame("body{background:#fff;margin:0;padding:0;color:#281f18}\nbody{}", core_minify::css_files($files));
$files = array($testfile1, $testfile2, $testfile3);
$this->assertStringStartsWith("body{background:#fff;margin:0;padding:0;color:#281f18}\nbody{}\n\n\n/* Cannot read CSS file ", @core_minify::css_files($files));
unlink($testfile1);
unlink($testfile2);
}
public function test_js() {
$js = "
function hm()
{
}
";
$this->assertSame("function hm(){}", core_minify::js($js));
$js = "function hm{}";
$result = core_minify::js($js);
$this->assertStringStartsWith("\ntry {console.log('Error: Minimisation of JavaScript failed!');} catch (e) {}", $result);
$this->assertContains($js, $result);
}
public function test_js_files() {
global $CFG;
$testfile1 = "$CFG->tempdir/test1.js";
$testfile2 = "$CFG->tempdir/test2.js";
$testfile3 = "$CFG->tempdir/test3.js";
$js1 = "
function hm()
{
}
";
$js2 = "function oh(){}";
file_put_contents($testfile1, $js1);
file_put_contents($testfile2, $js2);
$files = array($testfile1, $testfile2);
$this->assertSame("function hm(){}\nfunction oh(){}", core_minify::js_files($files));
$files = array($testfile1, $testfile2, $testfile3);
$this->assertStringStartsWith("function hm(){}\nfunction oh(){}\n\n\n// Cannot read JS file ", @core_minify::js_files($files));
unlink($testfile1);
unlink($testfile2);
}
}

View File

@ -82,6 +82,8 @@ Misc:
* httpsrequired() -> $PAGE->https_required()
* detect_munged_arguments() -> clean_param([...], PARAM_FILE)
* mygroupid() -> groups_get_all_groups()
* js_minify() -> core_minify::js_files()
* css_minify_css() -> core_minify::css_files()
YUI:
* moodle-core-notification has been deprecated with a recommendation of

View File

@ -95,7 +95,7 @@ if ($themerev <= 0 or $rev != $themerev) {
make_localcache_directory('theme', false);
js_write_cache_file_content($candidate, js_minify($theme->javascript_files($type)));
js_write_cache_file_content($candidate, core_minify::js_files($theme->javascript_files($type)));
// Verify nothing failed in cache file creation.
clearstatcache();
if (file_exists($candidate)) {