santa
santa

Reputation: 12512

Combine and minify CSS with PHP

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?

Upvotes: 0

Views: 2824

Answers (3)

Booboo
Booboo

Reputation: 44043

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

Upvotes: 1

Hackinet
Hackinet

Reputation: 3328

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.

Upvotes: 4

devsimsek
devsimsek

Reputation: 23

$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);
}

Upvotes: 1

Related Questions