How to Create a Laravel CSS-Minify Command

Share this article

In this article you’ll learn how to use Laravel’s Artisan command line tool, and how to create a customized command. Note that you need to be familiar with the Laravel framework to get the most of this article.

What are we building

In this tutorial we’re going to build a command to minify our css assets, which will be used like this:

cssmin 'output_path' 'file1'...'fileN' --comments --concat
  • output_path: (required) path to save the minified files, (style.css -> style.min.css).
  • file1 ... fileN: (required) list of files to minify.
  • --comments: (optional) add this option to keep comments.
  • --concat: (optional) concatenate the minified files into one file called all.min.css.

What is a Laravel Command

Artisan is the name of the command line utility in Laravel. It comes with a set of predefined commands, which you can list with php artisan list. If you want to show the help for a specific command, you can use php artisan help command.

Creating the Css Minifier Command

To create an artisan command, you can use the command:make command. This command accepts one argument:

  • name: the class name for the command.

and three options:

  • --command: the name that should be typed to run the command.
  • --path: by default the commands are stored within the app/commands folder, however, you can change that with this option.
  • --namespace: you can use this option to namespace your set of commands, e.g. in the command command:make, the make command is under the command namespace.

Now, to create our command we will use php artisan command:make CssMinCommand --command=cssminwhich will create a CssMinCommand.php file within our app/commands directory.

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

class CssminCommand extends Command{
    protected $name = 'cssmin';
    protected $description = 'Command description.';
    
    public function __construct(){
		parent::__construct();
	}
    
    public function fire(){
		//
	}
	
	protected function getArguments(){
		return array(
			array('example', InputArgument::REQUIRED, 'An example argument.'),
		);
	}
    
	protected function getOptions(){
		return array(
			array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
		);
	}
}

Our CssMinCommand class extends the Illuminate\Console\Command and overrides two methods ( getArguments, getOptions ).

  • getArguments: this function returns an array of arguments that should be passed to the command, (ex: the list of files that we pass to the cssmin command).
  • getOptions: returns a list of options or switches that you may pass to the command. (e.g. --comments).

Note: options may or may not have values, --comments is only a flag that returns true if it’s passed to the command, whereas --ouptput='public/assets' will return a value.

When your command is executed, the fire method is called, so this is where we need to put our command logic.

Registering the Command

if you try to run our command php artisan cssmin 'args' you’ll get a Command "cssmin" is not defined.

To register a command you need to add it to the artisan.php file:

Artisan::add( new CssMinCommand );

//or through the container
Artisan::add( App::make("CssMinCommand") );

If you don’t want to put your commands in the artisan.php file, you can create a separate file and include it, or if you’re creating a package you can register them in your Service Provider.

Arguments

In our getArguments method we will define our output and files.
To define an argument, we need to pass an array of values:

array( 'name', 'mode', 'description', 'defaultValue' )
  • name: key name to be used when retrieving arguments.
  • mode: can have one of the three options:

    • InputArgument::REQUIRED: argument is required.
    • InputArgument::OPTIONAL: argument is optional.
    • InputArgument::IS_ARRAY: argument accepts multiple values ( ex: file1...fileN).

    However, you can combine them like InputArgument::IS_ARRAY | InputArgument::REQUIRED (argument is required and must be an array).

  • description: useful when printing the command help.
  • defaultValue: if argument was not provided.

So our getArguments method will be:

protected function getArguments(){
        return array(
            array(
                'output', 
                InputArgument::REQUIRED,
                'Path to output directory'
            ),
            array(
                'files', 
                InputArgument::IS_ARRAY | InputArgument::OPTIONAL ,
                "List of css files to minify"
            ),
        );
    }

Note: when using the IS_ARRAY argument it should be the last one on the returned arguments array. (obviously).

Options

Our cssmin command will only have two options. To define an option we pass an array:

array('name', 'shortcut', 'mode', 'description', 'defaultValue')
  • name: the name of your option (ex: comments).
  • shortcut: a shorter version of your option (ex: --verbose and -v).
  • mode: can be one of the four options (InputOption::VALUE_IS_ARRAY, InputOption::VALUE_OPTIONAL, InputOption::VALUE_REQUIRED, InputOption::VALUE_NONE), the first three values are similar to the arguments.

    • VALUE_NONE: indicates that the option is a boolean flag ( ex: --verbose ).
  • description: useful when printing the command help.

  • defaultValue: if option value was not provided.

So our getOptions method will be:

protected function getOptions(){
        return array(
            array('comments', 'c', InputOption::VALUE_NONE, 'Don\'t strip comments' , null),
            array('concat', null, InputOption::VALUE_NONE, 'Concat the minified result to one file' , null),
        );
    }

Running the Command

When our fire method is called we need to gather our arguments and options. We can make a separate function to do that for us:

private function init(){
    // retrun an array
    $this->files = $this->argument('files');
    // return a string
    $this->output_path = $this->argument('output');
    // return true if passed, otherwise false
    $this->comments = $this->option('comments');
    // return true if passed, otherwise false
    $this->concat = $this->option('concat');
}

The argument and option methods take a key as an argument and return the appropriate value.

To keep our example clean and simple we will use this simple function with a small modification for the minification process.

private function minify( $css, $comments ){
        // Normalize whitespace
        $css = preg_replace( '/\s+/', ' ', $css );

        // Remove comment blocks, everything between /* and */, unless preserved with /*! ... */
        if( !$comments ){
            $css = preg_replace( '/\/\*[^\!](.*?)\*\//', '', $css );
        }//if
        
        // Remove ; before }
        $css = preg_replace( '/;(?=\s*})/', '', $css );

        // Remove space after , : ; { } */ >
        $css = preg_replace( '/(,|:|;|\{|}|\*\/|>) /', '$1', $css );

        // Remove space before , ; { } ( ) >
        $css = preg_replace( '/ (,|;|\{|}|\(|\)|>)/', '$1', $css );

        // Strips leading 0 on decimal values (converts 0.5px into .5px)
        $css = preg_replace( '/(:| )0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}.${2}${3}', $css );

        // Strips units if value is 0 (converts 0px to 0)
        $css = preg_replace( '/(:| )(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}0', $css );

        // Converts all zeros value into short-hand
        $css = preg_replace( '/0 0 0 0/', '0', $css );

        // Shortern 6-character hex color codes to 3-character where possible
        $css = preg_replace( '/#([a-f0-9])\\1([a-f0-9])\\2([a-f0-9])\\3/i', '#\1\2\3', $css );

        return trim( $css );
    }//minify

Now to process our arguments (files) we’re going to make a separate method to do the job.

private function processFiles(){
        // array of minified css
        $css_result = [];
        
        foreach ( $this->files as $file ) {
            //read file content
            $file_content = file_get_contents( $file );
            //minify CSS and add it to the result array
            $css_result[] = $this->minify( $file_content, $this->comments );
        }//foreach
        
        // if the concat flag is true
        if( $this->concat ){
            // join the array of minified css
            $css_concat = implode( PHP_EOL, $css_result );
            // save to file
            file_put_contents($this->output_path . '/all.min.css', $css_concat);
        }//if
        else{
            foreach ($css_result as $key => $css) {
                //remove '.css' to add '.min.css'
                $filename = basename( $this->files[$key], '.css' ) . '.min.css';
                // save to file
                file_put_contents($this->output_path . '/' . $filename, $css);
            }//for
        }//else

    }//processFiles

Finally, our fire method will only call the two methods:

public function fire(){
    $this->init();
    $this->processFiles();
}

Tip: You can also run an external command using the call method.

$this->call('command:name', array('argument' => 'foo', '--option' => 'bar'));

To test our command, we’re going to copy some css files into our public/css directory, and then run the command.

php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css'

php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css' --comments --concat

The first command will create two files (style.min.css, responsive.min.css) on the public/css directory.

Because we used the --comments and --concat flags, we’re going to get a file called all.min.css containing the two files with comments left.

Our command is not very descriptive and doesn’t give any messages or notifications!

Improving the Command

Before we continue, on the final GitHub repository I will create a new tag for our command so you can switch and test each one.

To make the command a little verbose, Laravel provides us with some output functions:

$this->line("This is a simple line message");
$this->info("This is an info");
$this->comment("This is a comment");
$this->question("This is a question");
$this->error("This is an error");

This will output:

Output

Beside just displaying messages, you can ask the user for information, ex:

$confirm = $this->confirm("'style.min.css' already exists, overwrite?", false);
$filename = $this->ask("Save the file as?", 'all.min.css');
$choice = $this->choice(
                        'Please select a level of minication:', 
                        [ 'minify all', 'leave comments' ], 
                        'default value'
                        );
                        
$password = $this->secret('Type your password to confirm:');
  • The confirm method takes two arguments, a question message and a default value if the user type something different than y/n.

  • The ask method will ask the user for an input instead of just y/n, and if it’s left empty, the default value is returned.

  • The choice method will give the user a numbered list to choose from, and if it’s left empty, the default value is returned.

  • The secret method will prompt the user with a question and hide the typing, but the user input will be returned.

In fact, Laravel is just making Symfony’s Console API simpler and more verbose, and there is so much more if you want dig in.

Let’s make our command more verbose and keep the user updated about the performed tasks.

private function processFiles(){
        $css_result = [];

        foreach ( $this->files as $file ) {
            $this->comment("'{$file}'");

            $this->info("Loading file");            
            //read file content
            $file_content = file_get_contents( $file );

            $this->info("minifying");
            //minify CSS and add it to the result array
            $css_result[] = $this->minify( $file_content, $this->comments );
        }//foreach

        if( $this->concat ){
            $this->comment("Concatenating into one file");

            $css_concat = implode( PHP_EOL, $css_result );

            $this->info("Saving to '{$this->output_path}/all.min.css'");
            file_put_contents($this->output_path . '/all.min.css', $css_concat);
        }//if
        else{
            foreach ($css_result as $key => $css) {
                //remove '.css' to add '.min.css'
                $filename = basename( $this->files[$key], '.css' ) . '.min.css';

                $this->comment("Saving '{$filename}'");
                file_put_contents($this->output_path . '/' . $filename, $css);
            }//for
        }//else
        
    }//processFiles

Our function now prints some useful messages to keep track of what’s going on.

command log

Note: This will be tagged as v2 of our command on the GitHub repository.

When creating an application, we are used to dumping the list of available routes (php artisan routes).

routes

Symfony provides a function that lets you print a such table easily. Check the documentation for an example. We’ll see next how we can use some Symfony Console Helpers.

Using the Symfony Console Helpers

To illustrate the use of some Symfony Helpers we will use the Progress Helper to keep the user updated about the job progress.

At the end of our init method we will require a progress from the HelperSet, then start our progress bar.

$this->progressbar = $this->getHelperSet()->get('progress');
$this->progressbar->start($this->output, count($this->files) );

The start method accepts two arguments, $this->output is a ConsoleOuput instance from the Symfony Console. The second argument is the maximum number of steps.

Every time we process a file in our processFiles method we will advance the progress bar by one step, and when the job is done we will end the progress bar and print a notification message.

private function processFiles(){
        $css_result = [];

        foreach ( $this->files as $file ) {
            //read file content
            $file_content = file_get_contents( $file );
            //minify CSS and add it to the result array
            $css_result[] = $this->minify( $file_content, $this->comments );
            // sleep for one second to see the effect
            //sleep(1);
            $this->progressbar->advance();
        }//foreach

        if( $this->concat ){
            $css_concat = implode( PHP_EOL, $css_result );
            file_put_contents($this->output_path . '/all.min.css', $css_concat);
        }//if
        else{
            foreach ($css_result as $key => $css) {
                //remove '.css' to add '.min.css'
                $filename = basename( $this->files[$key], '.css' ) . '.min.css';
                
                file_put_contents($this->output_path . '/' . $filename, $css);
            }//for
        }//else

        $this->progressbar->finish();
        $this->info('Done');
    }//processFiles

You can try the command with multiple files or uncomment the sleep function line to see a live effect.

progressbar

Note: This version will be tagged as v3 on the final repository.

Conclusion

In this article we’ve learned how create and extend Laravel commands. Laravel has a lot of built-in commands that you can explore, and you can also check our final repository on GitHub to test the final result. Questions? Comments? Would you like to see more Artisan Command tutorials? Let us know!

Frequently Asked Questions (FAQs) on Laravel CSS Minify Command

What is the purpose of minifying CSS in Laravel?

Minifying CSS in Laravel is a crucial step in optimizing your website or application. It involves the process of removing unnecessary characters such as spaces, comments, and line breaks from the CSS files. This process reduces the size of the CSS files, which in turn reduces the amount of data that needs to be transferred to the client. This can significantly improve the load time of your website or application, providing a better user experience.

How does Laravel Mix help in CSS minification?

Laravel Mix is a powerful tool that provides a fluent API for defining Webpack build steps for your Laravel application. It supports several common CSS and JavaScript pre-processors, including minification. By using Laravel Mix, you can easily minify your CSS files with a single command, without having to manually remove unnecessary characters. This not only saves time but also ensures that your CSS files are as optimized as possible.

Can I minify CSS files without using Laravel Mix?

Yes, you can minify CSS files without using Laravel Mix. There are several online tools and npm packages available that can help you minify your CSS files. However, using Laravel Mix is recommended as it integrates seamlessly with Laravel and provides a simple and convenient way to manage and optimize your CSS files.

What are the potential issues I might face while minifying CSS in Laravel?

While minifying CSS in Laravel is generally a straightforward process, you might encounter issues if your CSS files contain syntax errors. These errors can cause the minification process to fail, resulting in unoptimized CSS files. Therefore, it’s important to ensure that your CSS files are error-free before attempting to minify them.

How can I debug issues during CSS minification in Laravel?

If you encounter issues during CSS minification in Laravel, you can use Laravel Mix’s source maps feature to debug them. Source maps are files that map the minified CSS files back to the original source files, allowing you to easily trace and fix any issues.

Can I automate the process of CSS minification in Laravel?

Yes, you can automate the process of CSS minification in Laravel by using Laravel Mix’s versioning feature. This feature automatically minifies your CSS files whenever you run the production build command. This ensures that your CSS files are always optimized, without having to manually minify them each time.

How does CSS minification affect the performance of my Laravel application?

CSS minification can significantly improve the performance of your Laravel application. By reducing the size of your CSS files, you can reduce the amount of data that needs to be transferred to the client. This can result in faster load times, providing a better user experience.

Can I use Laravel Mix to minify other types of files?

Yes, besides CSS files, Laravel Mix can also be used to minify JavaScript files. This can further optimize your Laravel application, reducing the amount of data that needs to be transferred to the client.

What is the difference between minifying and concatenating CSS files?

Minifying CSS files involves removing unnecessary characters to reduce their size, while concatenating CSS files involves combining multiple CSS files into a single file. Both processes can help optimize your Laravel application, but they serve different purposes. Minifying reduces the size of each individual CSS file, while concatenating reduces the number of HTTP requests by combining multiple files into one.

How can I ensure that my minified CSS files are served correctly?

To ensure that your minified CSS files are served correctly, you can use Laravel Mix’s versioning feature. This feature appends a unique hash to the filenames of your minified CSS files, ensuring that the client always receives the latest version of your CSS files.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

artisancommandCSSlaravelminifyPHPphpoopSymfony Console
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week