1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-20 04:41:29 +02:00

Collapse "min" into project root and get unit tests working.

Fixes #472
This commit is contained in:
Steve Clay
2015-09-28 15:36:52 -04:00
parent 2720239c8c
commit e596b35fc4
89 changed files with 133 additions and 94 deletions

4
builder/.htaccess Normal file
View File

@@ -0,0 +1,4 @@
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</IfModule>

260
builder/_index.js Normal file
View File

@@ -0,0 +1,260 @@
/*!
* Minify URI Builder
*/
var MUB = {
_uid : 0,
_minRoot : '/min/?',
checkRewrite : function () {
var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1);
function fail() {
$('#minRewriteFailed')[0].className = 'topNote';
}
$.ajax({
url : '../f=' + testUri + '&' + (new Date()).getTime(),
success : function (data) {
if (data === '1') {
MUB._minRoot = '/min/';
$('span.minRoot').html('/min/');
} else
fail();
},
error : fail
});
},
/**
* Get markup for new source LI element
*/
newLi : function () {
return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>' +
' <button class="btn btn-danger btn-sm" title="Remove">x</button> <button class="btn btn-default btn-sm" title="Include Earlier">&uarr;</button>' +
' <button class="btn btn-default btn-sm" title="Include Later">&darr;</button> <span></span></li>';
},
/**
* Add new empty source LI and attach handlers to buttons
*/
addLi : function () {
$('#sources').append(MUB.newLi());
var li = $('#li' + MUB._uid)[0];
$('button[title=Remove]', li).click(function () {
$('#results').addClass('hide');
var hadValue = !!$('input', li)[0].value;
$(li).remove();
});
$('button[title$=Earlier]', li).click(function () {
$(li).prev('li').find('input').each(function () {
$('#results').addClass('hide');
// this = previous li input
var tmp = this.value;
this.value = $('input', li).val();
$('input', li).val(tmp);
MUB.updateAllTestLinks();
});
});
$('button[title$=Later]', li).click(function () {
$(li).next('li').find('input').each(function () {
$('#results').addClass('hide');
// this = next li input
var tmp = this.value;
this.value = $('input', li).val();
$('input', li).val(tmp);
MUB.updateAllTestLinks();
});
});
++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
if (! $('input', this)[0].value)
return;
var li = this;
$('span', this).html('');
var url = location.protocol + '//' + location.host + '/' +
$('input', this)[0].value.replace(/^\//, '');
$.ajax({
url : url,
complete : function (xhr, stat) {
if ('success' === stat)
$('span', li).html('<a href="#" class="btn btn-success btn-sm disabled">&#x2713;</a>');
else {
$('span', li).html('<button class="btn btn-warning btn-sm"><b>404! </b> recheck</button>')
.find('button').click(function () {
MUB.liUpdateTestLink.call(li);
});
}
},
dataType : 'text'
});
},
/**
* Check all source URLs
*/
updateAllTestLinks : function () {
$('#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) {
var i,
l = arr.length,
c = arr[0].charAt(pos);
if (c === '' || l === 1)
return c;
for (i = 1; i < l; ++i)
if (arr[i].charAt(pos) !== c)
return '';
return c;
},
/**
* Get the shortest URI to minify the set of source files
* @param Array sources URIs
*/
getBestUri : function (sources) {
var pos = 0,
base = '',
c;
while (true) {
c = MUB.getCommonCharAtPos(sources, pos);
if (c === '')
break;
else
base += c;
++pos;
}
base = base.replace(/[^\/]+$/, '');
var uri = MUB._minRoot + 'f=' + sources.join(',');
if (base.charAt(base.length - 1) === '/') {
// we have a base dir!
var basedSources = sources,
i,
l = sources.length;
for (i = 0; i < l; ++i) {
basedSources[i] = sources[i].substr(base.length);
}
base = base.substr(0, base.length - 1);
var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(',');
//window.console && console.log([uri, bUri]);
uri = uri.length < bUri.length ? uri : bUri;
}
return uri;
},
/**
* Create the Minify URI for the sources
*/
update : function () {
MUB.updateAllTestLinks();
var sources = [],
ext = false,
fail = false,
markup;
$('#sources input').each(function () {
var m, val;
if (! fail && this.value && (m = this.value.match(/\.(css|js)$/))) {
var thisExt = m[1];
if (ext === false)
ext = thisExt;
else if (thisExt !== ext) {
fail = true;
return alert('extensions must match!');
}
this.value = this.value.replace(/^\//, '');
if (-1 !== $.inArray(this.value, sources)) {
fail = true;
return alert('duplicate file!');
}
sources.push(this.value);
}
});
if (fail || ! sources.length)
return;
$('#groupConfig').val(" 'keyName' => array('//" + sources.join("', '//") + "'),");
var uri = MUB.getBestUri(sources),
uriH = uri.replace(/</, '&lt;').replace(/>/, '&gt;').replace(/&/, '&amp;');
$('#uriA').html(uriH)[0].href = uri;
if (ext === 'js') {
markup = '<script type="text/javascript" src="' + uriH + '"></script>';
} else {
markup = '<link type="text/css" rel="stylesheet" href="' + uriH + '" />';
}
$('#uriHtml').val(markup);
$('#results').removeClass('hide');
},
/**
* Handler for the "Add file +" button
*/
addButtonClick : function () {
$('#results').addClass('hide');
MUB.addLi();
MUB.updateAllTestLinks();
$('#update').removeClass('hide').click(MUB.update);
$('#sources li:last input')[0].focus();
},
/**
* Runs on DOMready
*/
init : function () {
$('#jsDidntLoad').remove();
$('#app').removeClass('hide');
$('#sources').html('');
$('#add button').click(MUB.addButtonClick);
// make easier to copy text out of
$('#uriHtml, #groupConfig, #symlinkOpt').click(function () {
this.select();
}).focus(function () {
this.select();
});
$('a.ext').attr({target:'_blank'});
if (location.hash) {
// make links out of URIs from bookmarklet
$('#getBm').addClass('hide');
var i = 0, found = location.hash.substr(1).split(','), l = found.length;
$('#bmUris').html('<p><strong>Found by bookmarklet:</strong> /</p>');
var $p = $('#bmUris p');
for (; i < l; i++) {
$p.append($('<a href=#></a>').text(found[i])[0]);
if (i < (l - 1)) {
$p.append(', /');
}
}
$('#bmUris a').click(function () {
MUB.addButtonClick();
$('#sources li:last input').val(this.innerHTML);
MUB.liUpdateTestLink.call($('#sources li:last')[0]);
$('#results').addClass('hide');
return false;
}).attr({title:'Add file +'});
} else {
// setup bookmarklet 1
$.ajax({
url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1),
success : function (code) {
$('#bm')[0].href = code
.replace('%BUILDER_URL%', location.href)
.replace(/\n/g, ' ');
},
dataType : 'text'
});
if ($.browser.msie) {
$('#getBm p:last').append(' Sorry, not supported in MSIE!');
}
MUB.addButtonClick();
}
// setup bookmarklet 2
$.ajax({
url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm2.js').substr(1),
success : function (code) {
$('#bm2')[0].href = code.replace(/\n/g, ' ');
},
dataType : 'text'
});
MUB.checkRewrite();
}
};
$(MUB.init);

36
builder/bm.js Normal file
View File

@@ -0,0 +1,36 @@
javascript:(function() {
var d = document
,uris = []
,i = 0
,o
,home = (location + '').split('/').splice(0, 3).join('/') + '/';
function add(uri) {
return (0 === uri.indexOf(home))
&& (!/[\?&]/.test(uri))
&& uris.push(escape(uri.substr(home.length)));
};
function sheet(ss) {
// we must check the domain with add() before accessing ss.cssRules
// otherwise a security exception will be thrown
if (ss.href && add(ss.href) && ss.cssRules) {
var i = 0, r;
while (r = ss.cssRules[i++])
r.styleSheet && sheet(r.styleSheet);
}
};
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++])
/* http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-DocumentStyle-styleSheets
document.styleSheet is a list property where [0] accesses the 1st element and
[outOfRange] returns null. In IE, styleSheets is a function, and also throws an
exception when you check the out of bounds index. (sigh) */
sheet(o);
if (uris.length)
window.open('%BUILDER_URL%#' + uris.join(','));
else
alert('No js/css files found with URLs within "'
+ home.split('/')[2]
+ '".\n(This tool is limited to URLs with the same domain.)');
})();

15
builder/bm2.js Normal file
View File

@@ -0,0 +1,15 @@
javascript:(function(){
var d = document
,c = d.cookie
,m = c.match(/\bminifyDebug=([^; ]+)/)
,v = m ? decodeURIComponent(m[1]) : ''
,p = prompt('Debug Minify URIs on ' + location.hostname + ' which contain:'
+ '\n(empty for none, space = OR, * = any string, ? = any char)', v)
;
if (p === null) return;
p = p.replace(/^\s+|\s+$/, '');
v = (p === '')
? 'minifyDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/'
: 'minifyDebug=' + encodeURIComponent(p) + '; path=/';
d.cookie = v;
})();

247
builder/index.php Normal file
View File

@@ -0,0 +1,247 @@
<?php
// check for auto-encoding
$encodeOutput = (function_exists('gzdeflate')
&& !ini_get('zlib.output_compression'));
// recommend $min_symlinks setting for Apache UserDir
$symlinkOption = '';
if (0 === strpos($_SERVER["SERVER_SOFTWARE"], 'Apache/')
&& preg_match('@^/\\~(\\w+)/@', $_SERVER['REQUEST_URI'], $m)
) {
$userDir = DIRECTORY_SEPARATOR . $m[1] . DIRECTORY_SEPARATOR;
if (false !== strpos(__FILE__, $userDir)) {
$sm = array();
$sm["//~{$m[1]}"] = dirname(__DIR__);
$array = str_replace('array (', 'array(', var_export($sm, 1));
$symlinkOption = "\$min_symlinks = $array;";
}
}
require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/../config.php';
if (! $min_enableBuilder) {
header('Content-Type: text/plain');
die('This application is not enabled. See https://github.com/mrclay/minify/blob/master/docs/BuilderApp.wiki.md');
}
if (isset($min_builderPassword)
&& is_string($min_builderPassword)
&& $min_builderPassword !== '') {
DooDigestAuth::http_auth('Minify Builder', array('admin' => $min_builderPassword));
}
$cachePathCode = '';
if (! isset($min_cachePath) && ! function_exists('sys_get_temp_dir')) {
$detectedTmp = Minify_Cache_File::tmp();
$cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';';
}
ob_start();
?>
<!DOCTYPE html>
<title>Minify URI Builder</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style>
body {margin:1em 60px;}
h1, h2, h3 {margin-left:-25px; position:relative;}
h1 {margin-top:0;}
#sources {margin:0; padding:0;}
#sources li {margin:0 0 0 40px}
#sources li input {margin-left:2px}
#add {margin:5px 0 1em 40px}
.hide {display:none}
#uriTable {border-collapse:collapse;}
#uriTable td, #uriTable th {padding-top:10px;}
#uriTable th {padding-right:10px;}
#groupConfig {font-family:monospace;}
b {color:#c00}
.topNote {background: #ff9; display:inline-block; padding:.5em .6em; margin:0 0 1em;}
.topWarning {background:#c00; color:#fff; padding:.5em .6em; margin:0 0 1em;}
.topWarning a {color:#fff;}
#jsDidntLoad {display:none;}
</style>
<body>
<?php if ($symlinkOption): ?>
<div class=topNote><strong>Note:</strong> It looks like you're running Minify in a user
directory. You may need the following option in /min/config.php to have URIs
correctly rewritten in CSS output:
<br><textarea id=symlinkOpt rows=3 cols=80 readonly><?php echo htmlspecialchars($symlinkOption); ?></textarea>
</div>
<?php endif; ?>
<p class=topWarning id=jsDidntLoad><strong>Uh Oh.</strong> Minify was unable to
serve Javascript for this app. To troubleshoot this,
<a href="https://github.com/mrclay/minify/blob/master/docs/Debugging.wiki.md">enable FirePHP debugging</a>
and request the <a id=builderScriptSrc href=#>Minify URL</a> directly. Hopefully the
FirePHP console will report the cause of the error.
</p>
<?php if ($cachePathCode): ?>
<p class=topNote><strong>Note:</strong> <code><?php echo
htmlspecialchars($detectedTmp); ?></code> was discovered as a usable temp directory.<br>To
slightly improve performance you can hardcode this in /min/config.php:
<code><?php echo htmlspecialchars($cachePathCode); ?></code></p>
<?php endIf; ?>
<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to
support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which
<a href="http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/"
>may reduce the benefit of proxy cache servers</a>.</p>
<h1>Minify URI Builder</h1>
<noscript><p class="topNote">Javascript and a browser supported by jQuery 1.2.6 is required
for this application.</p></noscript>
<div id=app class=hide>
<p>Create a list of Javascript or CSS files (or 1 is fine) you'd like to combine
and click [Update].</p>
<ol id=sources><li></li></ol>
<div id=add><button>Add file +</button></div>
<div id=bmUris></div>
<p><button class="btn btn-primary" id=update class=hide>Update</button></p>
<div id=results class=hide>
<h2>Minify URI</h2>
<p>Place this URI in your HTML to serve the files above combined, minified, compressed and
with cache headers.</p>
<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>HTML</th><td><input id=uriHtml type=text size=100 readonly></td></tr>
</table>
<h2>How to serve these files as a group</h2>
<p>For the best performance you can serve these files as a pre-defined group with a URI
like: <code><span class=minRoot>/min/?</span>g=keyName</code></p>
<p>To do this, add a line like this to /min/groupsConfig.php:</p>
<pre><code>return array(
<span style="color:#666">... your existing groups here ...</span>
<input id=groupConfig size=100 type=text readonly>
);</code></pre>
<p><em>Make sure to replace <code>keyName</code> with a unique key for this group.</em></p>
</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 bookmarks)</small></p>
</div>
<h3>Combining CSS files that contain <code>@import</code></h3>
<p>If your CSS files contain <code>@import</code> declarations, Minify will not
remove them. Therefore, you will want to remove those that point to files already
in your list, and move any others to the top of the first file in your list
(imports below any styles will be ignored by browsers as invalid).</p>
<p>If you desire, you can use Minify URIs in imports and they will not be touched
by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code></p>
<h3>Debug Mode</h3>
<p>When /min/config.php has <code>$min_allowDebugFlag = <strong>true</strong>;</code>
you can get debug output by appending <code>&amp;debug</code> to a Minify URL, or
by sending the cookie <code>minDebug=&lt;match&gt;</code>, where <code>&lt;match&gt;</code>
should be a string in the Minify URIs you'd like to debug. This bookmarklet will allow you to
set this cookie.</p>
<p><a id=bm2>Minify Debug</a> <small>(right-click, add to bookmarks)</small></p>
</div><!-- #app -->
<hr>
<p>Need help? Check the <a href="https://github.com/mrclay/minify/tree/master/docs">wiki</a>,
or post to the <a class=ext href="http://groups.google.com/group/minify">discussion
list</a>.</p>
<p><small>Powered by Minify <?php echo Minify::VERSION; ?></small></p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="jquery-1.6.3.min.js"><\/script>')</script>
<script>
(function () {
// workaround required to test when /min isn't child of web root
var src = "../?f=" + location.pathname.replace(/\/[^\/]*$/, '/_index.js').substr(1);
// load script immediately
document.write('<\script src="' + src + '"><\/script>');
$(function () {
$('#builderScriptSrc')[0].href = src;
// give Minify a few seconds to serve _index.js before showing scary red warning
setTimeout(function () {
if (! window.MUB) {
// Minify didn't load
$('#jsDidntLoad').css({display:'block'});
}
}, 3000);
// detection of double output encoding
var msg = '<\p class=topWarning><\strong>Warning:<\/strong> ';
var url = 'ocCheck.php?' + (new Date()).getTime();
$.get(url, function (ocStatus) {
$.get(url + '&hello=1', function (ocHello) {
if (ocHello != 'World!') {
msg += 'It appears output is being automatically compressed, interfering '
+ ' with Minify\'s own compression. ';
if (ocStatus == '1')
msg += 'The option "zlib.output_compression" is enabled in your PHP configuration. '
+ 'Minify set this to "0", but it had no effect. This option must be disabled '
+ 'in php.ini or .htaccess.';
else
msg += 'The option "zlib.output_compression" is disabled in your PHP configuration '
+ 'so this behavior is likely due to a server option.';
$(document.body).prepend(msg + '<\/p>');
} else
if (ocStatus == '1')
$(document.body).prepend('<\p class=topNote><\strong>Note:</\strong> The option '
+ '"zlib.output_compression" is enabled in your PHP configuration, but has been '
+ 'successfully disabled via ini_set(). If you experience mangled output you '
+ 'may want to consider disabling this option in your PHP configuration.<\/p>'
);
});
});
});
})();
</script>
</body>
<?php
$content = ob_get_clean();
if (!isset($min_cachePath)) {
$min_cachePath = '';
}
if (is_string($min_cachePath)) {
$cache = new Minify_Cache_File($min_cachePath, $min_cacheFileLocking);
} else {
$cache = $min_cachePath;
}
$env = new Minify_Env();
$sourceFactory = new Minify_Source_Factory($env, array(
'uploaderHoursBehind' => $min_uploaderHoursBehind,
));
$controller = new Minify_Controller_Page($env, $sourceFactory);
$server = new Minify($cache);
$server->serve($controller, array(
'content' => $content,
'id' => __FILE__,
'lastModifiedTime' => max(
// regenerate cache if any of these change
filemtime(__FILE__),
filemtime(__DIR__ . '/../config.php'),
filemtime(__DIR__ . '/../lib/Minify.php')
),
'minifyAll' => true,
'encodeOutput' => $encodeOutput,
));

4
builder/jquery-1.6.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

37
builder/ocCheck.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
/**
* AJAX checks for zlib.output_compression
*
* @package Minify
*/
require __DIR__ . '/../bootstrap.php';
$_oc = ini_get('zlib.output_compression');
// allow access only if builder is enabled
require __DIR__ . '/../config.php';
if (! $min_enableBuilder) {
header('Location: /');
exit;
}
if (isset($_GET['hello'])) {
// echo 'World!'
// try to prevent double encoding (may not have an effect)
ini_set('zlib.output_compression', '0');
HTTP_Encoder::$encodeToIe6 = true; // just in case
$he = new HTTP_Encoder(array(
'content' => 'World!'
,'method' => 'deflate'
));
$he->encode();
$he->sendAll();
} else {
// echo status "0" or "1"
header('Content-Type: text/plain');
echo (int)$_oc;
}

1
builder/rewriteTest.js Normal file
View File

@@ -0,0 +1 @@
1

43
builder/test.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
exit;
/* currently unused.
// capture PHP's default setting (may get overridden in config
$_oc = ini_get('zlib.output_compression');
// allow access only if builder is enabled
require __DIR__ . '/../config.php';
if (! $min_enableBuilder) {
exit;
}
if (isset($_GET['oc'])) {
header('Content-Type: text/plain');
echo (int)$_oc;
} elseif (isset($_GET['text']) && in_array($_GET['text'], array('js', 'css', 'fake'))) {
ini_set('zlib.output_compression', '0');
$type = ($_GET['text'] == 'js')
? 'application/x-javascript'
: "text/{$_GET['text']}";
header("Content-Type: {$type}");
echo 'Hello';
} elseif (isset($_GET['docroot'])) {
if (false === realpath($_SERVER['DOCUMENT_ROOT'])) {
echo "<p class=topWarning><strong>realpath(DOCUMENT_ROOT) failed.</strong> You may need "
. "to set \$min_documentRoot manually (hopefully realpath() is not "
. "broken in your environment).</p>";
}
if (0 !== strpos(realpath(__FILE__), realpath($_SERVER['DOCUMENT_ROOT']))) {
echo "<p class=topWarning><strong>DOCUMENT_ROOT doesn't contain this file.</strong> You may "
. " need to set \$min_documentRoot manually</p>";
}
if (isset($_SERVER['SUBDOMAIN_DOCUMENT_ROOT'])) {
echo "<p class=topNote><strong>\$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set.</strong> "
. "You may need to set \$min_documentRoot to this in config.php</p>";
}
}
//*/