define('MINIFY_STRING', '"(?:[^"\\\]|\\\.)*"|\'(?:[^\'\\\]|\\\.)*\'|`(?:[^`\\\]|\\\.)*`'); define('MINIFY_COMMENT_CSS', '/\*[\s\S]*?\*/'); define('MINIFY_COMMENT_HTML', ''); define('MINIFY_COMMENT_JS', '//[^\n]*'); define('MINIFY_PATTERN_JS', '/[^\n]+?/[gimuy]*'); 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', ']*?)?>[\s\S]*?|]*?)?>[\s\S]*?|]*?)?>[\s\S]*?|]*?)?>[\s\S]*?|]*?)?>[\s\S]*?'); // 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]; // Make sure URL does no contains `[ \n\t"']` character(s) $clean = t($part, $q); // Trim quote(s) $ok = strcspn($clean, " \n\t\"'") === strlen($clean); if ( $quote !== 1 && ( // substr($prev, -4) === 'url(' && preg_match('#\burl\($#', $prev) && $ok || // substr($prev, -1) === '=' && preg_match('#^' . $q . '[a-zA-Z_][\w-]*?' . $q . '$#', $part) ) ) { $part = $clean; } $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[bar="baz"]`, `#foo*` and `#foo:first-child` [^1] '#(?<=[\w])\s+(\*|\[|:[\w-]+)#', // Fix case for `[bar="baz"].foo`, `*.foo`, `:nth-child(2).foo` and `@media(foo: bar)and(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(?') { $output .= $part; } continue; } if ($part[0] === '<' && substr($part, -1) === '>') { $output .= fn_minify_html_union($part, $quote); } else if ($part[0] === '&' && substr($part, -1) === ';' && $part !== '<' && $part !== '>' && $part !== '&') { $output .= html_entity_decode($part); // Evaluate HTML entit(y|ies) } else { $output .= preg_replace('#\s+#', ' ', $part); } $prev = $part; } $output = str_replace(' |(\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 `` to `` [^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 && ( strpos($part, '//') === 0 || // 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) === '/' || preg_match('#\/[gimuy]*$#', $part))) { $output .= $part; } else if ( $part[0] === '"' && substr($part, -1) === '"' || $part[0] === "'" && substr($part, -1) === "'" || $part[0] === '`' && substr($part, -1) === '`' // ES6 ) { // 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); }