1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-13 09:34:54 +02:00

builder app improvements and bookmarklet

JSMin.php : + YUI and IE conditional comment preserving
This commit is contained in:
Steve Clay
2008-09-06 22:35:22 +00:00
parent 7bb535d79c
commit 9a5a67dfaf
11 changed files with 189 additions and 70 deletions

21
README
View File

@@ -16,16 +16,6 @@ directory: i.e. you will have: /home/user/www/public_html/min
you to the Minify URI Builder application, which will help you you to the Minify URI Builder application, which will help you
quickly start using Minify to serve content on your site. quickly start using Minify to serve content on your site.
UNIT TESTING:
1. Place the /min_extras/ directory as a child of your DOCUMENT_ROOT
directory: i.e. you will have: /home/user/www/public_html/min_extras
2. To run unit tests, access: http://yourdomain/min_extras/unit_tests/test_all.php
Other test_*.php files in that directory can be run to test individual
components more verbosely.
FILE ENCODINGS FILE ENCODINGS
@@ -35,3 +25,14 @@ encodings like ISO 8859/Windows-1252. By default Minify appends
Leading UTF-8 BOMs are stripped from all sources to prevent Leading UTF-8 BOMs are stripped from all sources to prevent
duplication in output files, and files are converted to Unix newlines. duplication in output files, and files are converted to Unix newlines.
UNIT TESTING:
1. Place the /min_extras/ directory as a child of your DOCUMENT_ROOT
directory: i.e. you will have: /home/user/www/public_html/min_extras
2. To run unit tests, access: http://yourdomain/min_extras/unit_tests/test_all.php
Other test_*.php files in that directory can be run to test individual
components more verbosely.

View File

@@ -1,41 +1,50 @@
// @todo update test links when reordering, app instructions
var MUB = { var MUB = {
_uid : 0 _uid : 0
/**
* Get markup for new source LI element
*/
,newLi : function () { ,newLi : function () {
return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>' return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>'
+ ' <button title="Remove">x</button> <button title="Include Earlier">&uarr;</button>' + ' <button title="Remove">x</button> <button title="Include Earlier">&uarr;</button>'
+ ' <button title="Include Later">&darr;</button> <span></span></li>'; + ' <button title="Include Later">&darr;</button> <span></span></li>';
} }
/**
* Add new empty source LI and attach handlers to buttons
*/
,addLi : function () { ,addLi : function () {
$('#sources').append(MUB.newLi()); $('#sources').append(MUB.newLi());
var li = $('#li' + MUB._uid)[0]; var li = $('#li' + MUB._uid)[0];
$('button[title=Remove]', li).click(function () { $('button[title=Remove]', li).click(function () {
$('#results').hide();
var hadValue = !!$('input', li)[0].value; var hadValue = !!$('input', li)[0].value;
$(li).remove(); $(li).remove();
hadValue && MUB.update();
}); });
$('button[title$=Earlier]', li).click(function () { $('button[title$=Earlier]', li).click(function () {
$(li).prev('li').find('input').each(function () { $(li).prev('li').find('input').each(function () {
$('#results').hide();
// this = previous li input // this = previous li input
var tmp = this.value; var tmp = this.value;
this.value = $('input', li).val(); this.value = $('input', li).val();
$('input', li).val(tmp); $('input', li).val(tmp);
MUB.updateAllTestLinks(); MUB.updateAllTestLinks();
MUB.update();
}); });
}); });
$('button[title$=Later]', li).click(function () { $('button[title$=Later]', li).click(function () {
$(li).next('li').find('input').each(function () { $(li).next('li').find('input').each(function () {
$('#results').hide();
// this = next li input // this = next li input
var tmp = this.value; var tmp = this.value;
this.value = $('input', li).val(); this.value = $('input', li).val();
$('input', li).val(tmp); $('input', li).val(tmp);
MUB.updateAllTestLinks(); MUB.updateAllTestLinks();
MUB.update();
}); });
}); });
++MUB._uid; ++MUB._uid;
} }
/**
* In the context of a source LI element, this will analyze the URI in
* the INPUT and check the URL on the site.
*/
,liUpdateTestLink : function () { // call in context of li element ,liUpdateTestLink : function () { // call in context of li element
if (! $('input', this)[0].value) if (! $('input', this)[0].value)
return; return;
@@ -58,9 +67,19 @@ var MUB = {
,dataType : 'text' ,dataType : 'text'
}); });
} }
/**
* Check all source URLs
*/
,updateAllTestLinks : function () { ,updateAllTestLinks : function () {
$('#sources li').each(MUB.liUpdateTestLink); $('#sources li').each(MUB.liUpdateTestLink);
} }
/**
* In a given array of strings, find the character they all have at
* a particular index
* @param Array arr array of strings
* @param Number pos index to check
* @return mixed a common char or '' if any do not match
*/
,getCommonCharAtPos : function (arr, pos) { ,getCommonCharAtPos : function (arr, pos) {
var i var i
,l = arr.length ,l = arr.length
@@ -72,6 +91,10 @@ var MUB = {
return ''; return '';
return c; return c;
} }
/**
* Get the shortest URI to minify the set of source files
* @param Array sources URIs
*/
,getBestUri : function (sources) { ,getBestUri : function (sources) {
var pos = 0 var pos = 0
,base = '' ,base = ''
@@ -103,6 +126,9 @@ var MUB = {
} }
return uri; return uri;
} }
/**
* Create the Minify URI for the sources
*/
,update : function () { ,update : function () {
MUB.updateAllTestLinks(); MUB.updateAllTestLinks();
var sources = [] var sources = []
@@ -139,21 +165,56 @@ var MUB = {
); );
$('#results').show(); $('#results').show();
} }
/**
* Handler for the "Add file +" button
*/
,addButtonClick : function () { ,addButtonClick : function () {
$('#results').hide();
MUB.addLi(); MUB.addLi();
MUB.updateAllTestLinks(); MUB.updateAllTestLinks();
$('#update').show().click(MUB.update); $('#update').show().click(MUB.update);
} }
/**
* Runs on DOMready
*/
,init : function () { ,init : function () {
$('#sources').html(''); $('#sources').html('');
$('#add button').click(MUB.addButtonClick); $('#add button').click(MUB.addButtonClick);
// make easier to copy text out of
$('#uriHtml, #groupConfig').click(function () { $('#uriHtml, #groupConfig').click(function () {
this.select(); this.select();
}).focus(function () { }).focus(function () {
this.select(); this.select();
}); });
$('a.ext').attr({target:'_blank'}); $('a.ext').attr({target:'_blank'});
MUB.addButtonClick(); if (location.hash) {
// make links out of URIs from bookmarklet
$('#getBm').hide();
$('#bmUris').html('<p><strong>Found by bookmarklet:</strong> /<a href=#>'
+ location.hash.substr(1).split(',').join('</a> | /<a href=#>')
+ '</a></p>'
);
$('#bmUris a').click(function () {
MUB.addButtonClick();
$('#sources li:last input').val(this.innerHTML)
MUB.liUpdateTestLink.call($('#sources li:last')[0]);
$('#results').hide();
return false;
}).attr({title:'Add file +'});
} else {
// copy bookmarklet code into href
var bmUri = location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1);
$.ajax({
url : '../?f=' + bmUri
,success : function (code) {
$('#bm')[0].href = code
.replace('%BUILDER_URL%', location.href)
.replace(/\n/g, ' ');
}
,dataType : 'text'
});
MUB.addButtonClick();
}
} }
}; };
window.onload = MUB.init; window.onload = MUB.init;

29
min/builder/bm.js Normal file
View File

@@ -0,0 +1,29 @@
javascript:(function() {
function add(uri) {
(0 === uri.indexOf(home))
&& (!/[\?&]/.test(uri))
&& uris.push(escape(uri.substr(home.length)));
}
function sheet(ss) {
ss.href && add(ss.href);
if (ss.cssRules) {
var i = 0, r;
while (r = ss.cssRules[i++])
r.styleSheet && sheet(r.styleSheet);
}
}
var d = document
,uris = []
,i = 0
,o
,home = (location + '').split('/').splice(0, 3).join('/') + '/';
while (o = d.getElementsByTagName('script')[i++])
o.src && !(o.type && /vbs/i.test(o.type)) && add(o.src);
i = 0;
while (o = d.styleSheets[i++])
sheet(o);
if (uris.length)
window.open('%BUILDER_URL%#' + uris.join(','));
else
alert('No minifiable resources found.');
})();

View File

@@ -45,6 +45,8 @@ and click [Update].</p>
<ol id=sources><li></li></ol> <ol id=sources><li></li></ol>
<div id=add><button>Add file +</button></div> <div id=add><button>Add file +</button></div>
<div id=bmUris></div>
<p><button id=update>Update</button></p> <p><button id=update>Update</button></p>
<div id=results> <div id=results>
@@ -53,7 +55,7 @@ and click [Update].</p>
<p>Place this URI in your HTML to serve the files above combined, minified, compressed and with cache headers.</p> <p>Place this URI in your HTML to serve the files above combined, minified, compressed and with cache headers.</p>
<table id=uriTable> <table id=uriTable>
<tr><th>URI</th><td><a id=uriA class=ext>/min</a> <small>(opens in new window)</small></td></tr> <tr><th>URI</th><td><a id=uriA class=ext>/min</a> <small>(opens in new window)</small></td></tr>
<tr><th>HTML</th><td><input id=uriHtml type=text size=80 readonly></td></tr> <tr><th>HTML</th><td><input id=uriHtml type=text size=100 readonly></td></tr>
</table> </table>
<h2>How to serve these files as a group</h2> <h2>How to serve these files as a group</h2>
@@ -63,12 +65,21 @@ and click [Update].</p>
<pre><code>return array( <pre><code>return array(
<span style="color:#666">... your existing groups here ...</span> <span style="color:#666">... your existing groups here ...</span>
<input id=groupConfig size=80 type=text readonly> <input id=groupConfig size=100 type=text readonly>
);</code></pre> );</code></pre>
<p><em>Make sure to replace <code>keyName</code> with a unique key for this group.</em></p> <p><em>Make sure to replace <code>keyName</code> with a unique key for this group.</em></p>
</div> </div>
<div id=getBm>
<h3>Find URIs on a Page</h3>
<p>You can use the bookmarklet below to fetch all CSS &amp; Javascript URIs from a page
on your site. When you active it, this page will open in a new window with a list of
available URIs to add.</p>
<p><a id=bm>Create Minify URIs</a> <small>(right-click, add to favorites/bookmarks)</small></p>
</div>
<hr> <hr>
<p>Need help? Search or post to the <a class=ext href="http://groups.google.com/group/minify">Minify discussion list</a>.</p> <p>Need help? Search or post to the <a class=ext href="http://groups.google.com/group/minify">Minify discussion list</a>.</p>
<p><small>This app is minified :) <a class=ext href="http://code.google.com/p/minify/source/browse/trunk/min/builder/index.php">view source</a></small></p> <p><small>This app is minified :) <a class=ext href="http://code.google.com/p/minify/source/browse/trunk/min/builder/index.php">view source</a></small></p>
@@ -81,7 +92,6 @@ and click [Update].</p>
document.write('<\script type="text/javascript" src="../?f=' + src + '"><\/script>'); document.write('<\script type="text/javascript" src="../?f=' + src + '"><\/script>');
</script> </script>
<!--[ This comment remains because the "[" makes it look like an IE conditional comment :) -->
<?php <?php
$serveOpts = array( $serveOpts = array(

View File

@@ -2,10 +2,14 @@
/** /**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin. * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
* *
* This is pretty much a direct port of jsmin.c to PHP with just a few * This is a direct port of jsmin.c to PHP with a few PHP performance tweaks and
* PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and * modifications to preserve some comments (see below). Also, rather than using
* outputs to stdout, this library accepts a string as input and returns another * stdin/stdout, JSMin::minify() accepts a string as input and returns another
* string as output. * string as output.
*
* Comments containing IE conditional compilation are preserved, as are multi-line
* comments that begin with "/*!" (for documentation purposes). In the latter case
* newlines are inserted around the comment to enhance readability.
* *
* PHP 5 or higher is required. * PHP 5 or higher is required.
* *
@@ -38,6 +42,7 @@
* *
* @package JSMin * @package JSMin
* @author Ryan Grove <ryan@wonko.com> * @author Ryan Grove <ryan@wonko.com>
* @author Steve Clay <steve@mrclay.org> (modifications)
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @license http://opensource.org/licenses/mit-license.php MIT License * @license http://opensource.org/licenses/mit-license.php MIT License
@@ -242,42 +247,54 @@ class JSMin {
} }
protected function next() { protected function next() {
$c = $this->get(); $get = $this->get();
if ($c === '/') { if ($get === '/') {
$commentContents = '';
switch($this->peek()) { switch($this->peek()) {
case '/': case '/':
// "//" comment
for (;;) { for (;;) {
$c = $this->get(); $get = $this->get();
$commentContents .= $get;
if (ord($c) <= self::ORD_LF) { if (ord($get) <= self::ORD_LF) {
return $c; return preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $commentContents)
? "/{$commentContents}"
: $get;
} }
} }
case '*': case '*':
// "/* */" comment
$this->get(); $this->get();
for (;;) { for (;;) {
switch($this->get()) { $get = $this->get();
switch($get) {
case '*': case '*':
if ($this->peek() === '/') { if ($this->peek() === '/') {
$this->get(); $this->get();
return ' '; if (0 === strpos($commentContents, '!')) {
// YUI Compressor style
return "\n/*" . substr($commentContents, 1) . "*/\n";
}
return preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $commentContents)
? "/*{$commentContents}*/" // IE conditional compilation
: ' ';
} }
break; break;
case null: case null:
throw new JSMinException('Unterminated comment.'); throw new JSMinException('Unterminated comment.');
} }
$commentContents .= $get;
} }
default: default:
return $c; return $get;
} }
} }
return $c; return $get;
} }
protected function peek() { protected function peek() {

View File

@@ -8,7 +8,7 @@ require 'JSMin.php';
/** /**
* Compress Javascript using Ryan Grove's JSMin class * Compress Javascript using Ryan Grove's JSMin class
* *
* @package Minify * @package Minify
* @author Stephen Clay <steve@mrclay.org> * @author Stephen Clay <steve@mrclay.org>
*/ */
@@ -19,28 +19,13 @@ class Minify_Javascript {
* *
* @param string $js * @param string $js
* *
* @param array $options available options: * @param array $options available options (none currently)
*
* 'preserveComments': (default true) multi-line comments that begin
* with "/*!" will be preserved with newlines before and after to
* enhance readability.
* *
* @return string * @return string
*/ */
public static function minify($js, $options = array()) public static function minify($js, $options = array())
{ {
if (isset($options['preserveComments']) return trim(JSMin::minify($js));
&& !$options['preserveComments']) {
return trim(JSMin::minify($js));
}
require_once 'Minify/CommentPreserver.php';
// recursive calls don't preserve comments
$options['preserveComments'] = false;
return Minify_CommentPreserver::process(
$js
,array('Minify_Javascript', 'minify')
,array($options)
);
} }
} }

View File

@@ -32,4 +32,18 @@ if (is.ua.indexOf('opera') >= 0) {
if (is.ua.indexOf('gecko') >= 0) { if (is.ua.indexOf('gecko') >= 0) {
is.ie = is.ns = false; is.ie = is.ns = false;
is.gecko = true; is.gecko = true;
} }
/*@cc_on
/*@if (@_win32)
document.write("OS is 32-bit, browser is IE.");
@else @*/
document.write("Browser is not IE (ie: is Firefox) or Browser is not 32 bit IE.");
/*@end
@*/
//@cc_on/*
alert("Hello !IE browser");
//@cc_on*/

View File

@@ -1,4 +1,3 @@
/* is.js /* is.js
(c) 2001 Douglas Crockford (c) 2001 Douglas Crockford
@@ -9,4 +8,9 @@ var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaE
* preserve this comment, too * preserve this comment, too
*/ */
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;} is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}
if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;} if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}/*@cc_on
/*@if (@_win32)
document.write("OS is 32-bit, browser is IE.");
@else @*/document.write("Browser is not IE (ie: is Firefox) or Browser is not 32 bit IE.");/*@end
@*///@cc_on/*
alert("Hello !IE browser");//@cc_on*/

View File

@@ -1,3 +0,0 @@
var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'}
is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}
if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}

View File

@@ -243,3 +243,18 @@
/* 33 */ is.ie = is.ns = false; /* 33 */ is.ie = is.ns = false;
/* 34 */ is.gecko = true; /* 34 */ is.gecko = true;
/* 35 */ } /* 35 */ }
/* 36 */
/* 37 */ /*@cc_on
/* 38 *| /*@if (@_win32)
/* 39 *| document.write("OS is 32-bit, browser is IE.");
/* 40 *| @else @*/
/* 41 */ document.write("Browser is not IE (ie: is Firefox) or Browser is not 32 bit IE.");
/* 42 */ /*@end
/* 43 *| @*/
/* 44 */
/* 45 */ //@cc_on/*
/* 46 *|
/* 47 *| alert("Hello !IE browser");
/* 48 *|
/* 49 *| //@cc_on*/
/* 50 */

View File

@@ -18,20 +18,6 @@ function test_Javascript()
echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n";
echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n";
} }
//$src = file_get_contents($thisDir . '/_test_files/js/before.js');
$minExpected = file_get_contents($thisDir . '/_test_files/js/before_noComments.min.js');
$minOutput = Minify_Javascript::minify($src, array(
'preserveComments' => false
));
$passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript');
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n";
echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n";
echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n";
}
} }
test_Javascript(); test_Javascript();