mirror of
https://github.com/mrclay/minify.git
synced 2025-08-11 00:24:11 +02:00
Merge pull request #541 from mrclay/static
Minify now allows static file serving
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,4 +8,5 @@
|
||||
/composer.lock
|
||||
/vendor
|
||||
/.php_cs.cache
|
||||
/static/[0-9]*
|
||||
/tests/compiler.jar
|
||||
|
@@ -1,5 +1,6 @@
|
||||
## Version 3.0.0 (unreleased)
|
||||
* The project root is now what is deployed as `min`
|
||||
* Adds feature to serve static files directly
|
||||
* Installation requires use of Composer to install dependencies
|
||||
* Removes JSMin+ (unmaintained, high memory usage)
|
||||
* Removes DooDigestAuth
|
||||
|
@@ -11,6 +11,10 @@ The stats above are from a [brief walkthrough](http://mrclay.org/index.php/2008/
|
||||
|
||||
Relative URLs in CSS files are rewritten to compensate for being served from a different directory.
|
||||
|
||||
## Static file serving
|
||||
|
||||
Version 3 allows [serving files directly from the filesystem](static/README.md) for much better performance. We encourage you to try this feature.
|
||||
|
||||
## Support
|
||||
|
||||
Post to the [Google Group](http://groups.google.com/group/minify).
|
||||
|
@@ -7,6 +7,12 @@
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Enable the static serving feature
|
||||
*/
|
||||
$min_enableStatic = false;
|
||||
|
||||
|
||||
/**
|
||||
* Allow use of the Minify URI Builder app. Only set this to true while you need it.
|
||||
*/
|
||||
|
@@ -4,7 +4,9 @@ The simple JSMin algorithm is the most reliable in PHP, but check the [CookBook]
|
||||
|
||||
## How fast is it?
|
||||
|
||||
Certainly not as fast as an HTTPd serving flat files. On a high-traffic site:
|
||||
If you [serve static files](https://github.com/mrclay/minify/blob/master/static/README.md), it's as fast as your web server, and you should do this for high-traffic sites.
|
||||
|
||||
The PHP-based server is not as fast, but still performs well thanks to an internal cache. Tips:
|
||||
|
||||
* **Use a reverse proxy** to cache the Minify URLs. This is by far the most important tip.
|
||||
* Revision your Minify URIs (so far-off Expires headers will be sent). One way to do this is using [groups](UserGuide.wiki.md#using-groups-for-nicer-urls) and the [Minify_groupUri()](UserGuide.wiki.md#far-future-expires-headers) utility function. Without this, clients will re-request Minify URLs every 30 minutes to check for updates.
|
||||
|
@@ -11,6 +11,11 @@ class Config
|
||||
*/
|
||||
public $enableBuilder = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $enableStatic = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
40
static/.htaccess
Normal file
40
static/.htaccess
Normal file
@@ -0,0 +1,40 @@
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</IfModule>
|
||||
|
||||
<FilesMatch "\.(js|css|less)$">
|
||||
FileETag MTime Size
|
||||
</FilesMatch>
|
||||
|
||||
<IfModule mod_gzip.c>
|
||||
mod_gzip_on yes
|
||||
mod_gzip_dechunk yes
|
||||
mod_gzip_keep_workfiles No
|
||||
mod_gzip_minimum_file_size 1000
|
||||
mod_gzip_maximum_file_size 1000000
|
||||
mod_gzip_maximum_inmem_size 1000000
|
||||
mod_gzip_item_include mime ^text/.*
|
||||
mod_gzip_item_include mime ^application/javascript$
|
||||
mod_gzip_item_include mime ^application/x-javascript$
|
||||
# Exclude old browsers and images since IE has trouble with this
|
||||
mod_gzip_item_exclude reqheader "User-Agent: .*Mozilla/4\..*\["
|
||||
mod_gzip_item_exclude mime ^image/.*
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/css text/javascript application/javascript application/x-javascript
|
||||
BrowserMatch ^Mozilla/4 gzip-only-text/html
|
||||
BrowserMatch ^Mozilla/4\.[0678] no-gzip
|
||||
BrowserMatch \bMSIE !no-gzip
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
|
||||
# You may need RewriteBase on some servers
|
||||
#RewriteBase /min/static
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ gen.php [QSA,L]
|
||||
</IfModule>
|
85
static/README.md
Normal file
85
static/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
# Static file serving
|
||||
|
||||
**Note:** This feature is new and not extensively tested.
|
||||
|
||||
Within this folder, Minify creates minified files on demand, serving them without the overhead of PHP at all.
|
||||
|
||||
For example, when a visitor requests a URL like `/min/static/1467089473/f=js/my-script.js`, Minify creates the directories `1467089473/f=js`, and saves the minified file `my-script.js` in it. On following requests, the file is served directly.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Make sure the `static` directory is writable by your server.
|
||||
|
||||
2. In `minify/config.php`, set `$min_enableStatic = true;`
|
||||
|
||||
3. Request the test script http://example.org/min/static/0/f=min/quick-test.js
|
||||
|
||||
This will create a new cache directory within `static` and redirect the browser to the new location, e.g. http://example.org/min/static/1467089473/f=min/quick-test.js.
|
||||
|
||||
You should see the minified script and on the server the `static` directory should contain a new subdirectory tree with the static file. Following requests will serve the file directly.
|
||||
|
||||
4. Delete the new subdirectory (e.g. `1467089473`) and refresh the browser.
|
||||
|
||||
You should be redirected to the new location where the file and cache directory has been recreated.
|
||||
|
||||
## Site integration
|
||||
|
||||
You don't want to hardcode any URLs. Instead we'll use functions in `lib.php`:
|
||||
|
||||
```php
|
||||
require_once __DIR__ . '/path/to/static/lib.php';
|
||||
|
||||
$static_uri = "/min/static";
|
||||
$query = "b=scripts&f=1.js,2.js";
|
||||
$type = "js";
|
||||
|
||||
$uri = Minify\StaticService\build_uri($static_uri, $query, $type);
|
||||
```
|
||||
|
||||
If you release a new build (or change any source file), you *must* clear the cache by deleting the entire directory:
|
||||
|
||||
```php
|
||||
require_once __DIR__ . '/path/to/static/lib.php';
|
||||
|
||||
Minify\StaticService\flush_cache();
|
||||
```
|
||||
|
||||
## URL rules
|
||||
|
||||
As URLs result in files being created, they are more strictly formatted.
|
||||
|
||||
* Arbitrary parameters (e.g. to bust a cache) are not permitted.
|
||||
* URLs must end with `.js` or `.css`.
|
||||
|
||||
If your URL does not end with `.js` or `.css`, you'll need to append `&z=.js` or `&z=.css` to the URL. E.g.:
|
||||
|
||||
* http://example.org/min/static/1467089473/g=home-scripts&z=.js
|
||||
* http://example.org/min/static/1467089473/f=styles.less&z=.css
|
||||
|
||||
Note that `Minify\StaticService\build_uri` handles this automatically for you.
|
||||
|
||||
URLs aren't canonical, so these URLs are all valid and will produce separate files:
|
||||
|
||||
* http://example.org/min/static/1467089473/f=one/two/three.js
|
||||
* http://example.org/min/static/1467089473/b=one/two&f=three.js
|
||||
* http://example.org/min/static/1467089473/f=three.js&b=one/two&z=.js
|
||||
|
||||
## Disable caching
|
||||
|
||||
You can easily switch to use the regular `min/` endpoint during development:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$query = "b=styles&f=minimal.less";
|
||||
$type = "css";
|
||||
|
||||
if ($use_static) {
|
||||
require_once __DIR__ . '/path/to/static/lib.php';
|
||||
$static_uri = "/min/static";
|
||||
$uri = Minify\StaticService\build_uri($static_uri, $query, $type);
|
||||
} else {
|
||||
$uri = "/min/?$query";
|
||||
}
|
||||
```
|
127
static/gen.php
Normal file
127
static/gen.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
// allows putting /static anywhere as long as you put a boostrap.php in it
|
||||
if (is_file(__DIR__ . '/bootstrap.php')) {
|
||||
$bootstrap_file = __DIR__ . '/bootstrap.php';
|
||||
} else {
|
||||
$bootstrap_file = __DIR__ . '/../bootstrap.php';
|
||||
}
|
||||
|
||||
$send_400 = function($content = 'Bad URL') {
|
||||
http_response_code(400);
|
||||
die($content);
|
||||
};
|
||||
|
||||
$send_301 = function($url) {
|
||||
http_response_code(301);
|
||||
header("Cache-Control: max-age=31536000");
|
||||
header("Location: $url");
|
||||
exit;
|
||||
};
|
||||
|
||||
$app = (require $bootstrap_file);
|
||||
/* @var \Minify\App $app */
|
||||
|
||||
if (!$app->config->enableStatic) {
|
||||
die('Minify static serving is not enabled. Set $min_enableStatic = true; in config.php');
|
||||
}
|
||||
|
||||
require __DIR__ . '/lib.php';
|
||||
|
||||
if (!is_writable(__DIR__)) {
|
||||
http_response_code(500);
|
||||
die('Directory is not writable.');
|
||||
}
|
||||
|
||||
// parse request
|
||||
// SCRIPT_NAME = /path/to/minify/static/gen.php
|
||||
// REQUEST_URI = /path/to/minify/static/1467084520/b=path/to/minify&f=quick-test.js
|
||||
|
||||
// "/path/to/minify/static"
|
||||
$root_uri = dirname($_SERVER['SCRIPT_NAME']);
|
||||
|
||||
// "/1467084520/b=path/to/minify&f=quick-test.js"
|
||||
$uri = substr($_SERVER['REQUEST_URI'], strlen($root_uri));
|
||||
|
||||
if (!preg_match('~^/(\d+)/(.*)$~', $uri, $m)) {
|
||||
http_response_code(404);
|
||||
die('File not found');
|
||||
}
|
||||
|
||||
// "1467084520"
|
||||
$requested_cache_dir = $m[1];
|
||||
|
||||
// "b=path/to/minify&f=quick-test.js"
|
||||
$query = $m[2];
|
||||
|
||||
// we basically want canonical querystrings because we make a file for each one.
|
||||
// manual parsing is the only way to do this. The MinApp controller will validate
|
||||
// these parameters anyway.
|
||||
$get_params = array();
|
||||
foreach (explode('&', $query) as $piece) {
|
||||
if (false === strpos($piece, '=')) {
|
||||
$send_400();
|
||||
}
|
||||
|
||||
list($key, $value) = explode('=', $piece, 2);
|
||||
if (!in_array($key, array('f', 'g', 'b', 'z'))) {
|
||||
$send_400();
|
||||
}
|
||||
|
||||
if (isset($get_params[$key])) {
|
||||
// already used
|
||||
$send_400();
|
||||
}
|
||||
|
||||
if ($key === 'z' && !preg_match('~^\.(css|js)$~', $value, $m)) {
|
||||
$send_400();
|
||||
}
|
||||
|
||||
$get_params[$key] = urldecode($value);
|
||||
}
|
||||
|
||||
$cache_time = Minify\StaticService\get_cache_time();
|
||||
if (!$cache_time) {
|
||||
http_response_code(500);
|
||||
die('Directory is not writable.');
|
||||
}
|
||||
|
||||
$app->env = new Minify_Env(array(
|
||||
'get' => $get_params,
|
||||
));
|
||||
$ctrl = $app->controller;
|
||||
$options = $app->serveOptions;
|
||||
$sources = $ctrl->createConfiguration($options)->getSources();
|
||||
if (!$sources) {
|
||||
http_response_code(404);
|
||||
die('File not found');
|
||||
}
|
||||
if ($sources[0]->getId() === 'id::missingFile') {
|
||||
$send_400("Bad URL: missing file");
|
||||
}
|
||||
|
||||
// we need URL to end in appropriate extension
|
||||
$type = $sources[0]->getContentType();
|
||||
$ext = ($type === Minify::TYPE_JS) ? '.js' : '.css';
|
||||
if (substr($query, - strlen($ext)) !== $ext) {
|
||||
$send_301("$root_uri/$cache_time/{$query}&z=$ext");
|
||||
}
|
||||
|
||||
// fix the cache dir in the URL
|
||||
if ($cache_time !== $requested_cache_dir) {
|
||||
$send_301("$root_uri/$cache_time/$query");
|
||||
}
|
||||
|
||||
$content = $app->minify->combine($sources);
|
||||
|
||||
// save and send file
|
||||
$file = __DIR__ . "/$cache_time/$query";
|
||||
if (!is_dir(dirname($file))) {
|
||||
mkdir(dirname($file), 0777, true);
|
||||
}
|
||||
|
||||
file_put_contents($file, $content);
|
||||
|
||||
header("Content-Type: $type;charset=utf-8");
|
||||
header("Cache-Control: max-age=31536000");
|
||||
echo $content;
|
68
static/lib.php
Normal file
68
static/lib.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Minify\StaticService;
|
||||
|
||||
/**
|
||||
* Build a URI for the static cache
|
||||
*
|
||||
* @param string $static_uri E.g. "/min/static"
|
||||
* @param string $query E.g. "b=scripts&f=1.js,2.js"
|
||||
* @param string $type "css" or "js"
|
||||
* @return string
|
||||
*/
|
||||
function build_uri($static_uri, $query, $type) {
|
||||
$static_uri = rtrim($static_uri, '/');
|
||||
$query = ltrim($query, '?');
|
||||
|
||||
$ext = ".$type";
|
||||
if (substr($query, - strlen($ext)) !== $ext) {
|
||||
$query .= "&z=$ext";
|
||||
}
|
||||
|
||||
$cache_time = get_cache_time();
|
||||
|
||||
return "$static_uri/$cache_time/$query";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the current cache directory within static/. E.g. "1467089473"
|
||||
*
|
||||
* @param bool $auto_create Automatically create the directory if missing?
|
||||
* @return null|string null if missing or can't create
|
||||
*/
|
||||
function get_cache_time($auto_create = true) {
|
||||
foreach (scandir(__DIR__) as $entry) {
|
||||
if (ctype_digit($entry)) {
|
||||
return $entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$auto_create) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$time = (string)time();
|
||||
if (!mkdir(__DIR__ . "/$time")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
function flush_cache() {
|
||||
$time = get_cache_time(false);
|
||||
if ($time) {
|
||||
remove_tree(__DIR__ . "/$time");
|
||||
}
|
||||
}
|
||||
|
||||
function remove_tree($dir) {
|
||||
$files = array_diff(scandir($dir), array('.', '..'));
|
||||
|
||||
foreach ($files as $file) {
|
||||
is_dir("$dir/$file") ? remove_tree("$dir/$file") : unlink("$dir/$file");
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
}
|
Reference in New Issue
Block a user