Forked from taufik-nurrohman/php-html-css-js-minifier.php
Created
January 16, 2017 07:54
-
-
Save dartokloning/ef13947885236a5609e5dc9ee4538842 to your computer and use it in GitHub Desktop.
PHP Function to Minify HTML, CSS and JavaScript
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
<?php | |
// Based on <https://github.com/mecha-cms/extend.minify> | |
define('MINIFY_STRING', '"(?:[^"\\\]|\\\.)*"|\'(?:[^\'\\\]|\\\.)*\''); | |
define('MINIFY_COMMENT_CSS', '/\*[\s\S]*?\*/'); | |
define('MINIFY_COMMENT_HTML', '<!\-{2}[\s\S]*?\-{2}>'); | |
define('MINIFY_COMMENT_JS', '//[^\n]*'); | |
define('MINIFY_PATTERN_JS', '\b/[^\n]+?/[gimuy]*\b'); | |
define('MINIFY_HTML', '<[!/]?[a-zA-Z\d:.-]+[\s\S]*?>'); | |
define('MINIFY_HTML_ENT', '&(?:[a-zA-Z\d]+|\#\d+|\#x[a-fA-F\d]+);'); | |
define('MINIFY_HTML_KEEP', '<pre(?:\s[^<>]*?)?>[\s\S]*?</pre>|<code(?:\s[^<>]*?)?>[\s\S]*?</code>|<script(?:\s[^<>]*?)?>[\s\S]*?</script>|<style(?:\s[^<>]*?)?>[\s\S]*?</style>|<textarea(?:\s[^<>]*?)?>[\s\S]*?</textarea>'); | |
// get URL | |
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] === 443 ? 'https' : 'http') . '://'; | |
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ""); | |
$url = $protocol . $host; | |
// escape character | |
define('X', "\x1A"); | |
// normalize line–break(s) | |
function n($s) { | |
return str_replace(["\r\n", "\r"], "\n", $s); | |
} | |
// trim once | |
function t($a, $b) { | |
if ($a && strpos($a, $b) === 0 && substr($a, -strlen($b)) === $b) { | |
return substr(substr($a, strlen($b)), 0, -strlen($b)); | |
} | |
return $a; | |
} | |
function fn_minify($pattern, $input) { | |
return preg_split('#(' . implode('|', $pattern) . ')#', $input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); | |
} | |
function fn_minify_css($input, $comment = 2, $quote = 2) { | |
if (!is_string($input) || !$input = n(trim($input))) return $input; | |
$output = $prev = ""; | |
foreach (fn_minify([MINIFY_COMMENT_CSS, MINIFY_STRING], $input) as $part) { | |
if (!trim($part)) continue; | |
if ($comment !== 1 && strpos($part, '/*') === 0 && substr($part, -2) === '*/') { | |
if ( | |
$comment === 2 && ( | |
// Detect special comment(s) from the third character. It should be a `!` or `*` → `/*! keep */` or `/** keep */` | |
strpos('*!', $part[2]) !== false || | |
// Detect license comment(s) from the content. It should contains character(s) like `@license` | |
stripos($part, '@licence') !== false || // noun | |
stripos($part, '@license') !== false || // verb | |
stripos($part, '@preserve') !== false | |
) | |
) { | |
$output .= $part; | |
} | |
continue; | |
} | |
if ($part[0] === '"' && substr($part, -1) === '"' || $part[0] === "'" && substr($part, -1) === "'") { | |
// Remove quote(s) where possible … | |
$q = $part[0]; | |
if ( | |
$quote !== 1 && ( | |
// <https://www.w3.org/TR/CSS2/syndata.html#uri> | |
substr($prev, -4) === 'url(' && preg_match('#\burl\($#', $prev) || | |
// <https://www.w3.org/TR/CSS2/syndata.html#characters> | |
substr($prev, -1) === '=' && preg_match('#^' . $q . '[a-zA-Z_][\w-]*?' . $q . '$#', $part) | |
) | |
) { | |
$part = t($part, $q); // trim quote(s) | |
} | |
$output .= $part; | |
} else { | |
$output .= fn_minify_css_union($part); | |
} | |
$prev = $part; | |
} | |
return trim($output); | |
} | |
function fn_minify_css_union($input) { | |
if (stripos($input, 'calc(') !== false) { | |
// Keep important white–space(s) in `calc()` | |
$input = preg_replace_callback('#\b(calc\()\s*(.*?)\s*\)#i', function($m) { | |
return $m[1] . preg_replace('#\s+#', X, $m[2]) . ')'; | |
}, $input); | |
} | |
$input = preg_replace([ | |
// Fix case for `#foo<space>[bar="baz"]`, `#foo<space>*` and `#foo<space>:first-child` [^1] | |
'#(?<=[\w])\s+(\*|\[|:[\w-]+)#', | |
// Fix case for `[bar="baz"]<space>.foo`, `*<space>.foo` and `@media<space>(foo: bar)<space>and<space>(baz: qux)` [^2] | |
'#(\*|\])\s+(?=[\w\#.])#', '#\b\s+\(#', '#\)\s+\b#', | |
// Minify HEX color code … [^3] | |
'#\#([a-f\d])\1([a-f\d])\2([a-f\d])\3\b#i', | |
// Remove white–space(s) around punctuation(s) [^4] | |
'#\s*([~!@*\(\)+=\{\}\[\]:;,>\/])\s*#', | |
// Replace zero unit(s) with `0` [^5] | |
'#\b(?:0\.)?0([a-z]+\b|%)#i', | |
// Replace `0.6` with `.6` [^6] | |
'#\b0+\.(\d+)#', | |
// Replace `:0 0`, `:0 0 0` and `:0 0 0 0` with `:0` [^7] | |
'#:(0\s+){0,3}0(?=[!,;\)\}]|$)#', | |
// Replace `background(?:-position)?:(0|none)` with `background$1:0 0` [^8] | |
'#\b(background(?:-position)?):(0|none)\b#i', | |
// Replace `(border(?:-radius)?|outline):none` with `$1:0` [^9] | |
'#\b(border(?:-radius)?|outline):none\b#i', | |
// Remove empty selector(s) [^10] | |
'#(^|[\{\}])(?:[^\{\}]+)\{\}#', | |
// Remove the last semi–colon and replace multiple semi–colon(s) with a semi–colon [^11] | |
'#;+([;\}])#', | |
// Replace multiple white–space(s) with a space [^12] | |
'#\s+#' | |
], [ | |
// [^1] | |
X . '$1', | |
// [^2] | |
'$1' . X, X . '(', ')' . X, | |
// [^3] | |
'#$1$2$3', | |
// [^4] | |
'$1', | |
// [^5] | |
'0', | |
// [^6] | |
'.$1', | |
// [^7] | |
':0', | |
// [^8] | |
'$1:0 0', | |
// [^9] | |
'$1:0', | |
// [^10] | |
'$1', | |
// [^11] | |
'$1', | |
// [^12] | |
' ' | |
], $input); | |
return trim(str_replace(X, ' ', $input)); | |
} | |
function fn_minify_html($input, $comment = 2, $quote = 1) { | |
if (!is_string($input) || !$input = n(trim($input))) return $input; | |
$output = $prev = ""; | |
foreach (fn_minify([MINIFY_COMMENT_HTML, MINIFY_HTML_KEEP, MINIFY_HTML, MINIFY_HTML_ENT], $input) as $part) { | |
if ($part === "\n") continue; | |
if (!trim($part) && ( | |
$prev[0] === '<' && $prev[1] !== '/' && substr($prev, -2) !== '/>' && !preg_match('#^<i(?:mg|nput)\b#', $prev) | |
)) continue; | |
if ($part !== ' ' && !trim($part) || $comment !== 1 && strpos($part, '<!--') === 0) { | |
// Detect IE conditional comment(s) by its closing tag … | |
if ($comment === 2 && substr($part, -12) === '<![endif]-->') { | |
$output .= $part; | |
} | |
continue; | |
} | |
if ($part[0] === '<' && substr($part, -1) === '>') { | |
$output .= fn_minify_html_union($part, $quote); | |
} else if ($part[0] === '&' && substr($part, -1) === ';') { | |
$output .= html_entity_decode($part); // Evaluate HTML entit(y|ies) | |
} else { | |
$output .= preg_replace('#\s+#', ' ', $part); | |
} | |
$prev = $part; | |
} | |
$output = str_replace(' </', '</', $output); | |
// Force space with ` ` and line–break with `
` | |
return str_ireplace([' ', ' ', '
', '
'], [' ', ' ', "\n", "\n"], trim($output)); | |
} | |
function fn_minify_html_union($input, $quote) { | |
if ( | |
strpos($input, ' ') === false && | |
strpos($input, "\n") === false && | |
strpos($input, " ") === false | |
) return $input; | |
global $url; | |
return preg_replace_callback('#<\s*([^\/\s]+)\s*(?:>|(\s[^<>]+?)\s*>)#', function($m) use($quote, $url) { | |
if (isset($m[2])) { | |
// Minify inline CSS(s) | |
if (stripos($m[2], ' style=') !== false) { | |
$m[2] = preg_replace_callback('#( style=)([\'"]?)(.*?)\2#i', function($m) { | |
return $m[1] . $m[2] . fn_minify_css($m[3]) . $m[2]; | |
}, $m[2]); | |
} | |
// Minify URL(s) | |
if (strpos($m[2], '://') !== false) { | |
$m[2] = str_replace([ | |
$url . '/', | |
$url . '?', | |
$url . '&', | |
$url . '#', | |
$url . '"', | |
$url . "'" | |
], [ | |
'/', | |
'?', | |
'&', | |
'#', | |
'/"', | |
"/'" | |
], $m[2]); | |
} | |
$a = 'a(sync|uto(focus|play))|c(hecked|ontrols)|d(efer|isabled)|hidden|ismap|loop|multiple|open|re(adonly|quired)|s((cop|elect)ed|pellcheck)'; | |
$a = '<' . $m[1] . preg_replace([ | |
// From `a="a"`, `a='a'`, `a="true"`, `a='true'`, `a=""` and `a=''` to `a` [^1] | |
'#\s(' . $a . ')(?:=([\'"]?)(?:true|\1)?\2)#i', | |
// Remove extra white–space(s) between HTML attribute(s) [^2] | |
'#\s*([^\s=]+?)(=(?:\S+|([\'"]?).*?\3)|$)#', | |
// From `<img />` to `<img/>` [^3] | |
'#\s+\/$#' | |
], [ | |
// [^1] | |
' $1', | |
// [^2] | |
' $1$2', | |
// [^3] | |
'/' | |
], str_replace("\n", ' ', $m[2])) . '>'; | |
return $quote !== 1 ? fn_minify_html_union_attr($a) : $a; | |
} | |
return '<' . $m[1] . '>'; | |
}, $input); | |
} | |
function fn_minify_html_union_attr($input) { | |
if (strpos($input, '=') === false) return $input; | |
return preg_replace_callback('#=(' . MINIFY_STRING . ')#', function($m) { | |
$q = $m[1][0]; | |
if (strpos($m[1], ' ') === false && preg_match('#^' . $q . '[a-zA-Z_][\w-]*?' . $q . '$#', $m[1])) { | |
return '=' . t($m[1], $q); | |
} | |
return $m[0]; | |
}, $input); | |
} | |
function fn_minify_js($input, $comment = 2, $quote = 2) { | |
if (!is_string($input) || !$input = n(trim($input))) return $input; | |
$output = $prev = ""; | |
foreach (fn_minify([MINIFY_COMMENT_CSS, MINIFY_STRING, MINIFY_COMMENT_JS, MINIFY_PATTERN_JS], $input) as $part) { | |
if (!trim($part)) continue; | |
if ($comment !== 1 && ( | |
substr($part, 0, 2) === '//' || // Remove inline comment(s) | |
strpos($part, '/*') === 0 && substr($part, -2) === '*/' | |
)) { | |
if ( | |
$comment === 2 && ( | |
// Detect special comment(s) from the third character. It should be a `!` or `*` → `/*! keep */` or `/** keep */` | |
strpos('*!', $part[2]) !== false || | |
// Detect license comment(s) from the content. It should contains character(s) like `@license` | |
stripos($part, '@licence') !== false || // noun | |
stripos($part, '@license') !== false || // verb | |
stripos($part, '@preserve') !== false | |
) | |
) { | |
$output .= $part; | |
} | |
continue; | |
} | |
if ($part[0] === '"' && substr($part, -1) === '"' || $part[0] === "'" && substr($part, -1) === "'") { | |
// TODO: Remove quote(s) where possible … | |
$output .= $part; | |
} else { | |
$output .= fn_minify_js_union($part); | |
} | |
$prev = $part; | |
} | |
return $output; | |
} | |
function fn_minify_js_union($input) { | |
return preg_replace([ | |
// Remove white–space(s) around punctuation(s) [^1] | |
'#\s*([!%&*\(\)\-=+\[\]\{\}|;:,.<>?\/])\s*#', | |
// Remove the last semi–colon and comma [^2] | |
'#[;,]([\]\}])#', | |
// Replace `true` with `!0` and `false` with `!1` [^3] | |
'#\btrue\b#', '#\bfalse\b#', '#\b(return\s?)\s*\b#', | |
// Replace `new Array(x)` with `[x]` … [^4] | |
'#\b(?:new\s+)?Array\((.*?)\)#', '#\b(?:new\s+)?Object\((.*?)\)#' | |
], [ | |
// [^1] | |
'$1', | |
// [^2] | |
'$1', | |
// [^3] | |
'!0', '!1', '$1', | |
// [^4] | |
'[$1]', '{$1}' | |
], $input); | |
} | |
/** | |
* Backward Compatibility | |
* ---------------------- | |
*/ | |
function minify_css(...$lot) { | |
return fn_minify_css(...$lot); | |
} | |
function minify_html(...$lot) { | |
return fn_minify_html(...$lot); | |
} | |
function minify_js(...$lot) { | |
return fn_minify_js(...$lot); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment