0

I came across a script that combines multiple CSS files and minifies them before outputting it together as one file, however it doesn't seem to work.

<?php
header('Content-type: text/css');
ob_start("compress");

    function compress( $minify )
    {
        /* remove comments */
        $minify = preg_replace( '!/*[^*]**+([^/][^*]**+)*/!', '', $minify );

        /* remove tabs, spaces, newlines, etc. */
         $minify = str_replace( array("rn", "r", "n", "t", '  ', '    ', '    '), '', $minify );

        return $minify;
    }

    /* css files for combining */
    include('reset.css');
    include('application.css');
    include('responsive.css');

ob_end_flush();

Usage:

<link href="assets/css/minified.css.php" rel="stylesheet">

I am able to see the file but when I click into it I don't see CSS, it's just blank. What am I missing?

4
  • * is a special char in regex so needs to be escaped. Short snippet of $minify would help question... might be looking for /\*[^*]*\*+([^/][^*]\**+)* Commented Jun 4, 2021 at 14:04
  • This is all there was. I'm not even sure how the CSS code is being passed into this function. I think something is missing. Commented Jun 4, 2021 at 15:02
  • ob_start calls it and takes the full page generation as the parameter. So reset.css, application.css, and responsive.css are stitched together then replacements are run on them Commented Jun 4, 2021 at 15:08
  • If you don't cache the result and don't set headers to cache it, this will most likely cause every request of every visitor to combine & minify your stylesheets and make your website very slow. Commented Jun 4, 2021 at 16:18

3 Answers 3

5
+500

I wrote an optimized version, you just need to pass the filenames:


Code:

<?php

class CssMinifer{

    private $fileNames = [];

    function __construct($fileNames){
        $this->fileNames = $fileNames;
    }

    private function fileValidator($fileName){

        $fileParts = explode('.',$fileName);
        $fileExtension  = end($fileParts);

        if(strtolower($fileExtension) !== "css"){
            throw new Exception("Invalid file type. The extension for the file $fileName is $fileExtension.");
        }

        if(!file_exists($fileName)){
            throw new Exception("The given file $fileName does not exists.");
        }

    }

    private function setHeaders(){
        header('Content-Type: text/css');
    }

    public function minify(){

        $this->setHeaders();

        $minifiedCss = "";
        $fileNames = $this->fileNames;

        foreach ($fileNames as $fileName){
            try{
                $this->fileValidator($fileName);
                $fileContent = file_get_contents($fileName);
                $minifiedCss = $minifiedCss . $this->minify_css($fileContent);
            } catch(\Exception $e) {
                echo 'Message: ' .$e->getMessage();
                return false;
            }
        }

        return $minifiedCss;

    }

    //Credits for minify_css @ https://gist.github.com/Rodrigo54/93169db48194d470188f
    private function minify_css($input) {
        if(trim($input) === "") return $input;
        return preg_replace(
            array(
                // Remove comment(s)
                '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')|\/\*(?!\!)(?>.*?\*\/)|^\s*|\s*$#s',
                // Remove unused white-space(s)
                '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\'|\/\*(?>.*?\*\/))|\s*+;\s*+(})\s*+|\s*+([*$~^|]?+=|[{};,>~]|\s(?![0-9\.])|!important\b)\s*+|([[(:])\s++|\s++([])])|\s++(:)\s*+(?!(?>[^{}"\']++|"(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')*+{)|^\s++|\s++\z|(\s)\s+#si',
                // Replace `0(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)` with `0`
                '#(?<=[\s:])(0)(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)#si',
                // Replace `:0 0 0 0` with `:0`
                '#:(0\s+0|0\s+0\s+0\s+0)(?=[;\}]|\!important)#i',
                // Replace `background-position:0` with `background-position:0 0`
                '#(background-position):0(?=[;\}])#si',
                // Replace `0.6` with `.6`, but only when preceded by `:`, `,`, `-` or a white-space
                '#(?<=[\s:,\-])0+\.(\d+)#s',
                // Minify string value
                '#(\/\*(?>.*?\*\/))|(?<!content\:)([\'"])([a-z_][a-z0-9\-_]*?)\2(?=[\s\{\}\];,])#si',
                '#(\/\*(?>.*?\*\/))|(\burl\()([\'"])([^\s]+?)\3(\))#si',
                // Minify HEX color code
                '#(?<=[\s:,\-]\#)([a-f0-6]+)\1([a-f0-6]+)\2([a-f0-6]+)\3#i',
                // Replace `(border|outline):none` with `(border|outline):0`
                '#(?<=[\{;])(border|outline):none(?=[;\}\!])#',
                // Remove empty selector(s)
                '#(\/\*(?>.*?\*\/))|(^|[\{\}])(?:[^\s\{\}]+)\{\}#s'
            ),
            array(
                '$1',
                '$1$2$3$4$5$6$7',
                '$1',
                ':0',
                '$1:0 0',
                '.$1',
                '$1$3',
                '$1$2$4$5',
                '$1$2$3',
                '$1:0',
                '$1$2'
            ),
            $input);
    }


}

$fileNames  = ['css1.css','css2.css'];

$cssMinifier = new CssMinifer($fileNames);
echo $cssMinifier->minify();

Brief Explanation:

You pass an array of CSS filenames and initialize the class with it. You then call the minify() method on the class, which returns you the minified and combined css.

$fileNames  = ['css1.css','css2.css'];

$cssMinifier = new CssMinifer($fileNames);
echo $cssMinifier->minify();

In the minify() method, the call goes to setHeaders() method, which sets the header to Content-Type: text/css. Setting headers must always happen before any output.

We then loop over the supplied fileNames inside the minify() method, before minification, the fileName is passed to fileValidator() method, which checks if the file is indeed a CSS file and if the file exists (typos happen :) ), if not throws an exception that is caught and displayed and code is halted.

Still, inside the loop, if the file is valid, we move forward. The file is read and passed as a string to the minify_css() method which returns the minified CSS. Which is then appended to the $minifiedCss variable outside the loop. After completion of the loop, you are left with the big minified and combined CSS string, which is returned to you, that you can simply echo. No need to use ob_ functions.

Sign up to request clarification or add additional context in comments.

1 Comment

Nice one! Really sped-up my load time. Thanks a bunch!
1
$css = preg_replace(array('/\s*(\w)\s*{\s*/','/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/','/\n/','/\s*}\s*/'), 
       array('$1{ ','$1$3;',"",'} '), $css);

This snippet collapses every line. It leaves spaces within the property definition of the CSS code. This will minify CSS but not the comments.

Let me give an example; if you want to load existing file and minify it you can do it by readfile function:

$css = readfile("filename.css");
$css = preg_replace(array('/\s*(\w)\s*{\s*/','/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/','/\n/','/\s*}\s*/'),
       array('$1{ ','$1$3;',"",'} '), $css);

If you need a function which will save minified css you can use this;

function minify(string $file)
    {
        $css = readfile($file); // reading file and its contents
        $css = preg_replace(array('/\s*(\w)\s*{\s*/', '/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/', '/\n/', '/\s*}\s*/'),
            array('$1{ ', '$1$3;', "", '} '), $css); // Collapsing every line
        file_put_contents($file, $css); // Saving the minified css to file
    }

So if you want multiple css files and minify every file at once here's the snippet;

function minify(array $files)
{
    // if we want to minify multiple css files and return it once we can use arrays!
    $return = array();
    foreach ($files as $file) {
        $css = readfile($file); // reading file and its contents
        $css = preg_replace(array('/\s*(\w)\s*{\s*/', '/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/', '/\n/', '/\s*}\s*/'),
            array('$1{ ', '$1$3;', "", '} '), $css); // Collapsing every line
        array_push($return, $css);
    }
    // Returning all css in once!
    return implode("\n", $return);
}

To Save every minified css in one file;

function minify(array $files, string $output = "all.min.css")
{
    // if we want to minify multiple css files and return it once we can use arrays!
    $return = array();
    foreach ($files as $file) {
        $css = readfile($file); // reading file and its contents
        $css = preg_replace(array('/\s*(\w)\s*{\s*/', '/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/', '/\n/', '/\s*}\s*/'),
            array('$1{ ', '$1$3;', "", '} '), $css); // Collapsing every line
        array_push($return, $css);
    }
    // Returning all css in once!
    $implode = implode("\n", $return);
    // Putting all minidied css into output file
    file_put_contents($output, $implode);
}

6 Comments

Oh, I like that idea. How could I adopt this to combine multiple CSS files into one as well?
Yes you can. I will add a little function to the answer.
This is a great explanation, thank you! However I'm still a bit confused. I put this on my page: $files = array("incl/lib/styles.css", "incl/css/style.css"); function minify(array $files, string $output = "all.min.css") { ... file_put_contents($output, $implode); } minify($files, $output = "all.min.css"); but all I get is the output of both CSS files on my page.
so in last function we minify every css file into all.min.css. you need to require all.min.css. ex: <link rel="stylesheet" href="all.min.css"> Sorry for the delay.
oh okay i will look the error today. sorry for the delay.
|
1

There is a free, public domain web service CSS minifier at https://cssminifier.com/, which seems to do a better job of minifying the CSS than either of the two (so far) regular expression replacement-based answers. I have included a benchmark following the usage section. Admittedly, there could be a slight additional overhead in making the POST request. But this might be the better solution if this compression request is made once and the final compressed result is instead saved as a file that will be henceforth statically served, which would be my recommendation.

Usage

<?php
header('Content-type: text/css');

function getMinified($content) {
    $postdata = array('http' => array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query( array('input' => $content) ) ) );
    return file_get_contents('https://cssminifier.com/raw', false, stream_context_create($postdata));
}

function minify($infiles)
{
    $outfiles = [];
    foreach ($infiles as $infile) {
        $outfiles[] = getMinified(file_get_contents($infile));
    }
    return implode("\n", $outfiles);
}

$minified = minify(['reset.css', 'application.css','responsive.css']);
echo $minified;

Benchmark

<?php
function getMinified($content) {
    $postdata = array('http' => array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query( array('input' => $content) ) ) );
    return file_get_contents('https://cssminifier.com/raw', false, stream_context_create($postdata));
}

function minify($infiles)
{
    $outfiles = [];
    foreach ($infiles as $infile) {
        $outfiles[] = getMinified(file_get_contents($infile));
    }
    return implode("\n", $outfiles);
}

$css = file_get_contents('w2ui-1.5.rc1.css');
echo "Original CSS file length: ", strlen($css), "\n";

$minified = preg_replace(array('/\s*(\w)\s*{\s*/','/\s*(\S*:)(\s*)([^;]*)(\s|\n)*;(\n|\s)*/','/\n/','/\s*}\s*/'),
       array('$1{ ','$1$3;',"",'} '), $css);
echo "Compressed length (method 1): ", strlen($minified), "\n";

$minified = preg_replace(
            array(
                // Remove comment(s)
                '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')|\/\*(?!\!)(?>.*?\*\/)|^\s*|\s*$#s',
                // Remove unused white-space(s)
                '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\'|\/\*(?>.*?\*\/))|\s*+;\s*+(})\s*+|\s*+([*$~^|]?+=|[{};,>~]|\s(?![0-9\.])|!important\b)\s*+|([[(:])\s++|\s++([])])|\s++(:)\s*+(?!(?>[^{}"\']++|"(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')*+{)|^\s++|\s++\z|(\s)\s+#si',
                // Replace `0(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)` with `0`
                '#(?<=[\s:])(0)(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)#si',
                // Replace `:0 0 0 0` with `:0`
                '#:(0\s+0|0\s+0\s+0\s+0)(?=[;\}]|\!important)#i',
                // Replace `background-position:0` with `background-position:0 0`
                '#(background-position):0(?=[;\}])#si',
                // Replace `0.6` with `.6`, but only when preceded by `:`, `,`, `-` or a white-space
                '#(?<=[\s:,\-])0+\.(\d+)#s',
                // Minify string value
                '#(\/\*(?>.*?\*\/))|(?<!content\:)([\'"])([a-z_][a-z0-9\-_]*?)\2(?=[\s\{\}\];,])#si',
                '#(\/\*(?>.*?\*\/))|(\burl\()([\'"])([^\s]+?)\3(\))#si',
                // Minify HEX color code
                '#(?<=[\s:,\-]\#)([a-f0-6]+)\1([a-f0-6]+)\2([a-f0-6]+)\3#i',
                // Replace `(border|outline):none` with `(border|outline):0`
                '#(?<=[\{;])(border|outline):none(?=[;\}\!])#',
                // Remove empty selector(s)
                '#(\/\*(?>.*?\*\/))|(^|[\{\}])(?:[^\s\{\}]+)\{\}#s'
            ),
            array(
                '$1',
                '$1$2$3$4$5$6$7',
                '$1',
                ':0',
                '$1:0 0',
                '.$1',
                '$1$3',
                '$1$2$4$5',
                '$1$2$3',
                '$1:0',
                '$1$2'
            ),
            $css);
echo "Compressed length (method 2): ", strlen($minified), "\n";

$infiles = ['w2ui-1.5.rc1.css'];
$minified = minify($infiles);
echo "Compressed length (web service): ", strlen($minified), "\n";
//file_put_contents('customgrid.css.min', $minified);

Prints:

Original CSS file length: 107093
Compressed length (method 1): 97768
Compressed length (method 2): 92506
Compressed length (web service): 89348

1 Comment

This is so 2020ish using a third party service via HTTP. It's not bad, but one has to keep in mind that if the website / service goes offline OR the client doesn't have internet access the "minification" won't work. I'm sure you know this but I'm leaving this here for others.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.