Jump to content

Combine and Minify CSS Files


Boost
 Share

Recommended Posts

This is my approach to combining CSS files for improved page speed. The implementation is straightforward; you only need to add one PHP file to your server and make a few tweaks.

My setup:
-site/
  -templates/
    - inc/
    - styles/
    

_init.php
Include the CombinedCSS.php file at the top of my _init.php:

include_once('./inc/CombinedCSS.php');

_main.php
Add the following to the <head> section:

<?php 
	$cssFiles = [
    	paths()->templates . 'styles/uikit.min.css',
    	paths()->templates . 'styles/uikit.ext.css',
    	paths()->templates . 'styles/main.css'
	];
		
	echo CombinedCSS::CSS($cssFiles);
?>

CombinedCSS.php

<?php namespace ProcessWire;

/**
 * Import the RuntimeException class for throwing runtime exceptions.
 */
use RuntimeException;

/**
 * Class CombinedCSS
 * 
 * A class for combining and minifying CSS files and generating a link tag for the combined CSS.
 */
class CombinedCSS {
    
    /**
     * @var string|null The output directory for saving the combined CSS file.
     */
    private static $outputDirectory;

    /**
     * @var string|null Hash of the concatenated content of CSS files.
     */
    private static $filesHash;

    /**
     * Initializes the output directory if it is not already set.
     */
    private static function initOutputDirectory() {
        if (!isset(self::$outputDirectory)) {
            self::$outputDirectory = paths()->templates . 'styles/';
        }
    }

    /**
     * Checks if a directory is writable.
     * 
     * @param string $directory The directory path to check.
     * @return bool True if the directory is writable, false otherwise.
     */
    private static function isWritableDirectory($directory) {
        // Check if the directory exists
        if (!is_dir($directory)) {
            return false;
        }

        // Check if the directory is writable
        if (!is_writable($directory)) {
            return false;
        }

        return true;
    }

    /**
     * Combines and minifies an array of CSS files.
     * 
     * @param array $files An array of CSS file paths.
     * @return string The combined and minified CSS content.
     */
    private static function combineAndMinifyCSS(array $files): string {
        $combinedCss = '';

        foreach ($files as $file) {
            // Read the content of each CSS file
            $cssContent = file_get_contents($file);

            // Minify the CSS content (you can replace this with your preferred minification logic)
            $minifiedCss = self::minifyCSS($cssContent);

            // Append the minified CSS to the combined CSS content
            $combinedCss .= $minifiedCss;
        }

        return $combinedCss;
    }

    /**
     * Minifies CSS content.
     * 
     * @param string $css The CSS content to be minified.
     * @return string The minified CSS content.
     */
    private static function minifyCSS(string $css): string {
        // Replace this with your preferred CSS minification logic
        // Example: removing comments, extra whitespaces, etc.
        $minifiedCss = preg_replace('/\/\*.*?\*\//s', '', $css);
        $minifiedCss = preg_replace('/\s+/', ' ', $minifiedCss);

        return $minifiedCss;
    }

    /**
     * Generates a unique filename for the combined CSS file based on the provided file paths.
     * 
     * @param array $files An array of CSS file paths.
     * @return string The generated combined CSS filename.
     */
    private static function generateCombinedFilename(array $files): string {
        return md5(implode('', $files)) . '.css';
    }

    /**
     * Saves the combined CSS content to a file.
     * 
     * @param string $filename The filename for the combined CSS file.
     * @param string $content The combined CSS content to be saved.
     * 
     * @throws RuntimeException If there is an error writing the file to disk.
     */
    private static function saveToFile(string $filename, string $content): void {
        // Save the combined CSS content to the specified file
        $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $filename;

        if (file_put_contents($filePath, $content) === false) {
            $error = error_get_last();
            $errorMessage = isset($error['message']) ? $error['message'] : 'Unknown error';
            throw new RuntimeException('Failed to write the combined CSS file to disk. Error: ' . $errorMessage);
        }
    }

    /**
     * Generates a link tag for the combined CSS file based on the provided file paths.
     * 
     * @param array $files An array of CSS file paths.
     * @return string The link tag for the combined CSS file.
     * @throws RuntimeException If the output directory is not writable.
     */
    public static function CSS(array $files): string {
        // Initialize the output directory
        self::initOutputDirectory();

        // Ensure the output directory is writable
        if (!self::isWritableDirectory(self::$outputDirectory)) {
            throw new RuntimeException('The output directory is not writable.');
        }

        // Combine and minify CSS files
        $combinedCss = self::combineAndMinifyCSS($files);

        // Check if the files have changed by comparing their hash
        $currentFilesHash = md5($combinedCss);
        $combinedFilename = '';

        if ($currentFilesHash !== self::$filesHash) {
            // If the files have changed, generate a unique filename for the combined CSS file
            $combinedFilename = self::generateCombinedFilename($files);

            // Save the combined CSS content to a file
            self::saveToFile($combinedFilename, $combinedCss);

            // Update the files hash
            self::$filesHash = $currentFilesHash;
        } else {
            // If the files have not changed, check if the file exists on disk
            $combinedFilename = self::generateCombinedFilename($files);
            $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $combinedFilename;

            if (!file_exists($filePath)) {
                // If the file doesn't exist, save the combined CSS content to a file
                self::saveToFile($combinedFilename, $combinedCss);
            }
        }

        // Return the link tag
        return '<link rel="stylesheet" href="' . urls()->templates . 'styles/' . $combinedFilename . '">';
    }
}

Notes:

  • Ensure that you have writing permissions for the $outputDirectory location.
  • The combined CSS file will be generated and saved only if the files have changed or if the file doesn't exist on disk.
  • The order of the combined file will adhere to the sequence specified in the array.
  • Modify the hardcoded paths based on your requirements.

Any suggestions for improvements are welcome!

Cheers!

  • Like 1
Link to comment
Share on other sites

18 minutes ago, Boost said:

Any suggestions for improvements are welcome!

18 minutes ago, Boost said:

This is my approach to combining CSS files for improved page speed

I don't think that this is true any more with HTTP2 because browsers can download multiple files at once. That's why I didn't implement something like this in RockFrontend. But I'm not sure 🙂 

Link to comment
Share on other sites

1 hour ago, bernhard said:

I don't think that this is true any more with HTTP2 because browsers can download multiple files at once. That's why I didn't implement something like this in RockFrontend. But I'm not sure 🙂 

Me neither 🙂

I had to write the script because https://pagespeed.web.dev/ complained about my multiple files.

  • Sad 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...