We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Assets minification, regenerated every time?

Hey all,

Very happy with the direction Phalcon is taking. I've used it with success on a test internal tool, and we're about to use it on a major project.

With the recent asset minification fix (https://github.com/phalcon/cphalcon/issues/811), it appears the final file is generated every time the page loads. Is this supposed to happen, or am I missing anything? I know the Jsmin/Cssmin are fast as they are part of Phalcon core, but I am worried every landing will result in going through the join/filters.

Is there a way for Phalcon to distinguish between currently valid minified files and the ones that should be regenerated?

I had suggested an approach to this which automatically generates the minified file from a hash sum of the last modified time of all included files. That way, if any one of the files has a different "last modified" time, the minifier would know it needs to be recompiled, otherwise, it would just load the min file that's already there. The entire discussion is here: https://github.com/phalcon/cphalcon/issues/549

Essentially, this is how it would work:

$assets->addCss('file.css'); $assets->addCss('file2'.css);

Compiled file would be output file name + md5(filemtime('file.css').filemtime('file2.css'));, so something like 'minified_3q4un9028tb26782638794c238462.css'.

On next request, the file name is recalculated. If the filemtime is the same, the hash stays the same, and we can do a check like if (is_readable($outputFile)) { // echo this minified CSS file } however, if the filemtime is different, the hash is different, and the file automatically doesn't exist. This signals the minifier that a recompile is in order and it regenerates everything.

This ensures the minification is rerun only after every file change (in other words only when needed) and also makes sure your clients get the most up to date CSS and JS at all times, because the filename (the hash part) changes on every change and isn't stuck in cache - basically, this is like versioning. This is how I've had my own minifier for years and it's never let me down.

I've created a new issue for it here: https://github.com/phalcon/cphalcon/issues/925

Thanks Bruno, I had seen your comment in the ticket #549, but didn't see if it may have been implemented. Thanks for creating a new ticket, although wondering, is it similar to https://github.com/phalcon/cphalcon/issues/639 (from https://github.com/phalcon/cphalcon/issues/549#issuecomment-18561130 bullet point #3)?

Indeed. It doesn't seem like it was added, though, or I just can't find a more in-depth explanation on the topic.

Not every feature request is immediately added ;)



51.3k

So what's the current behaviour - is it regenerated every time or not?

Thanks!

It is regenerated every time as clearly indicated by the status "open" of the NFR request mentioned some posts earlier. It would, however, be a simple task to implement the desired behavior in PHP.



8.6k

My approach is much simpler. I only check if the file exists and I return the path. Doing hashes when having, say, 20 files is very punishing on the server cpu. I just attach to the filename the release version(which auto-updates itself) so I don't have to manualy erase the generated files(which is still a valid option though :) )

<?php

namespace KyoLib\Assets;

use Phalcon\Assets\Manager as PhManager;

class Manager extends PhManager
{

    public function output($collection, $callback, $type=null){

        if( is_file($collection->getTargetPath())) {
            if ( $type == 'css' ) {
                echo \Phalcon\Tag::stylesheetLink( $collection->getTargetUri() );
            }
            if ( $type == 'js' ) {
                echo \Phalcon\Tag::javascriptInclude( $collection->getTargetUri() );
            }
        }else{
            parent::output($collection, $callback, $type);
        }
    }
}


88

Is it still regenerated every time?

@vlad4800

Can you please describe how do you use this method? Where do you call it? I have $this->assets->outputCss() in the view... Sorry for the dummy question. I am new to Phalcon. Thank you

Issue will hardly get fixed because if you use filemtime to check for modification everytime, every file, it will add an extra overhead to the php script. Slowly, automated things will render Phalcon slower and slower.

One approach I use is to check for cache existence, if it doesn't exist generate it. It is good for development. But in production, best way is to use a cache builder shell script and bind it to a git hook. This will kill as much overhead as possible.

<?php

use Phalcon\Mvc\Controller;
use Phalcon\Assets\Filters\Jsmin;

class ControllerBase extends Controller {

//    public function onConstruct() {
//
//    }
    // Executed after every found action
    public function afterExecuteRoute($dispatcher) {
        $asset = $this->assets
                ->collection('global')
                ->setTargetPath('assets/all.min.js')
                ->setTargetUri('assets/all.min.js');

        //if file exist in cache serve it directly
        if (ENV_PROD && is_file('assets/all.min.js'))
            $asset->addJs('assets/all.min.js');
        else {
            //order matter
            $asset->addJs('js/script/global.js')
                    ->join(true)
                    ->addFilter(new Jsmin());
        }

        //autodetect controller specific script
        $controller = $this->dispatcher->getControllerName();

        $asset = $this->assets->collection('controller')
                ->setTargetPath("assets/c.$controller.min.js")
                ->setTargetUri("assets/c.$controller.min.js");

        if (ENV_PROD && is_file("assets/c.$controller.min.js"))
            $asset->addJs("assets/c.$controller.min.js");
        else {
            $asset->addJs("js/script/c.$controller.js")
                    ->addFilter(new Jsmin());
        }

        //autodetect action specific script
        $action = $this->dispatcher->getActionName();

        $asset = $this->assets->collection('action')
                ->setTargetPath("assets/$controller/a.$action.min.js")
                ->setTargetUri("assets/$controller/a.$action.min.js");

        if (ENV_PROD && is_file("assets/$controller/a.$action.min.js"))
            $asset->addJs("assets/$controller/a.$action.min.js");
        else {
            //create dir if not exist
            if (!is_dir("assets/$controller")) {
                mkdir("assets/$controller");
                chmod("assets/$controller", 0770);
            }

            $asset->addJs("js/script/$controller/a.$action.js")
                    ->addFilter(new Jsmin());
        }
    }

}