Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Minify now allows static file serving
With slightly altered URLs, Minify can cache files so they're served directly from the filesystem instead of through PHP. A simple library helps create URLs and clearing the cache. See `static/README.md` for details.
- Loading branch information
Showing
10 changed files
with
340 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ | |
/composer.lock | ||
/vendor | ||
/.php_cs.cache | ||
/static/[0-9]* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |