Asset And Script Manager

What kind of features will be included in 2.0?

Do we have SASS, LESS and CoffeeScript compiling?

What about CSS & JavaScript combining / minifying?

There are some pretty nice features here: https://github.com/meenie/munee

We’re aiming at supporting minifying and compiling out of the box. Not sure if anything other than CSS and JS will be supported out of the box but implementing any compiling routing should be relatively straightforward. Note that this part if Yii2 isn’t even in alpha state so it can be changed still.

That is really awesome news Samdark :)

One of the main reasons why I use the AssetManager as little as possible these days.

How about adding support for hooks?

So that people can add application specific asset related tasks?

Yes, that’s the idea. Not hooks but callbacks.

Music to my ears. :)

This is excellent news indeed. I missed that feature a lot!

That’s what we’re going to do:

This is very good feature. I have an implementation which is close to what you planning, so I can think it can be useful to describe it. Also at the end are some thoughts on your design and some suggestions.

My client script packages configuration now looks like this:




    return array(

        'yiicore' => array(

            'depends' => array(

                //"standard" packages

                'yii', 

                'yiiactiveform',

                ...

            ),

        ),

        'zii.js' => array(

            'basePath' => 'zii.widgets.assets',

            'js' => array(

                'gridview/jquery.yiigridview.js',

                'listview/jquery.yiilistview.js',

            ),

        ),

        'myExt' => array(

            'basePath' => 'common.extensions.go.assets',

            'js' => array(

                'jquery.blockUI.js',

                'json2.js',

                ...

            ),

            'depends' => array(

                'jquery',

                'yiicore',

                'jquery.ui',

                'zii.js',

            ),

        ),

        'site' => array(

            'baseUrl' => '',

            'js' => array(

                'js/easyTooltip.js',

                'js/ie-hover.js',

            ),

            'css' => array(

                'css/custom-theme/jquery-ui-1.8.4.custom.css',

                'css/yii/gridview/styles.css',

                ...

            ),

            'depends' => array(

                'myExt',

            ),

        ),

    );



Package registration looks like this:




    Yii::app()->clientScript->registerPackageExt('site');



Client script config:




    'clientScript'=>array(

        'class' => 'app.components.AppClientScript',

        'packages'=> require(dirname(__FILE__).'/components/clientScript.php'),

        'minifyCss' => true, //keep it enabled, used in testprod configs

        'minifyJs' => true, //keep it enabled, used in testprod configs

        'useGoogleScripts' => false

    ),



Current features:

  1. No "random" registerScriptFile() calls - use packages only (extensions still use registerScriptFile() calls, but this is ignored)

  2. Package can have "baseUrl" (url on the server, for js/css from public folder) or "basePath" (assets inside the protected folder, should be published first)

  3. Package can contain both js and css (better not to do this, see below)

  4. Only one package can be registered (see explanation below)

  5. Scripts like jquery can be included from the google CDN (‘useGoogleScripts’ setting in the config)

  6. Depending on the config settings we can keep full js/css or automatically minify them (I use modified minscript (http://www.yiiframework.com/extension/minscript/) extension for this)

  7. There are dependencies and packages can include other packages.

Bonus feature (not related to packages) - special implementation of beginScript / endScript, so the js code in a view can be added like this:

<?php Yii::app()->clientScript->beginScript('billing-subscribe'); ?>


<script type="text/javascript">


MyObject.config({


    subdomain: '<?=$subdomain?>',


    data: "some data"


});


</script>


<?php Yii::app()->clientScript->endScript();?>

What can be improved/changed in my implementation:

  • Point (3) - “package can have both js and css” - this is probably not good and it is better to have separate packages with js/ts/css/less. We can specify ‘type’ for each package and based on the type we can know how to process this package (compress and join or compile first and then join, etc).

  • Point (4) - only one package can be registered - this limitation was initially implemented by intention. When scripts are minified then the page contains only one combined js and one css. But now I think it is better to remove it and allow to register additional packages, so the page can contain more then one combined js or css (this is useful in some cases).

  • Point (6) - now packages can only be minified on-the-fly and it is better to have also a console command to generate minified packages off-line.

In general I do not think that my current implementation can be reused (here is my client script class), because it rely on the minscript extension.

But I think that following ideas can be taken into consideration:

  1. Separate package handling logic (packages description and registration) from the build process.

Now I see ‘process’ keys in the packages configuration and it would be better to remove it.

Instead we can have some ‘type’ or ‘processor’ key which will point to the builder object class. So we can have different builders like JSBuilder (compress and glue js), CSSBuilder (compress and glue css), LESSBuilder (complie less and then compress and glue), etc. Builders can then be used to do on-the-fly or offline package compilation.

  1. How to deal with gluing of js / css files. I think the most simple way is to build each package independently, taking into account dependencies. Then we can include into the web page one meta-package which depends on all other packages or include individual packages.

  2. Allow to easily switch between compressed and non-compressed scripts

  3. Allow to use scripts both from public folder (no need to publish then to assets) and from the protected folder (publish to assets)

  4. Consider adding a support for scripts inclusion from google CDN. It would be good to have an option to easily switch from local versions to CDN.

  1. What if I want to use Google closure with extreme optimizations for package X but with normal optimizations for package Y?

  2. There’s a map setting for it.

Then we just specify different processors for these packages.

Basically the idea is to specify processor class or class+params instead of callback.

This also means that we need to have files of the same type for one package, so the structure will be:




return array(

    'packageX' => array(

        'basePath' => '@app/scripts'

        'processor' => 'GoogleClosureCompilerProcessor'

        // or 

        'processor' => array(

           'class' => 'GoogleClosureCompilerProcessor',

           'param1' => 'xxx'

        ),

    ),

    'packageY' => array(

        'basePath' => '@app/scripts'

        'processor' => 'DirectPublishProcessor'

    ),

);




With map setting as it described now it can be confusing, for example:




'package1' => array(

        'basePath' => '@app/scripts'

        'map' => array(

            'one.js' => 'common.js',

            'two.js' => 'common.js',

            '*.js' => 'all.js',

        ),

        'process' => array(

            // do we need to duplicate map structure from the above?

            'one.js' => function($fileName, $outputDir) ...

            'two.js' => function($fileName, $outputDir) ...

            '*.js' => function($fileName, $outputDir) ...

        ),

    ),



Here if we can specify ‘*.js’ then it should be also possible to specify individual scripts like ‘one.js’, yes?

Also will ‘*.js’ be recursive (if there are subfolders below the app/scripts)?

So I think it is better to have packages for files of the same type. By default package will include all appropriate files from the ‘basePath’.

We can also allow to specify individual files to include or constructs like ‘*’ and ‘**’ to include all files / recursively include all files. But this is optional and can be postponed - the developer can create appropriate folders structure, so one package = one folder + other packages as dependencies (or just dependencies).

And the ‘map’ parameter should go to processor configuration:




return array(

    'packageJsX' => array(

        'basePath' => '@app/scripts'

        // all scripts from the base path except these:

        'exclude' => array(

           'subfolder/script1.js',

           'subfolder/script2.js',

        ), 

        'processor' => array(

           'class' => 'GoogleClosureCompilerProcessor',

           'map' => 'all.js',  // add complied files to 'all.js'

        ),

    ),

    'packageJsY' => array(

        'basePath' => '@app/scripts'

        // include two scripts from the base path:

        'include' => array(

           'subfolder/script1.js',

           'subfolder/script2.js',

        ), 

        'processor' => 'DirectPublishJSProcessor'

    ),

    'packageJsAll' => array(

        'depends' => array(

            'packageJsX',

            'packageJsY',

         ),

         'processor' => false //or not specified

    ),

);



Ah. I thought you’re talking about what’s at github. We’ve decided to use processor classes instead of callbacks.

Actually yes, this is about what at the github. Sorry, I didn’t read the full discussion and thought that description at the top is up-to-date.

It is not completely clear how processors will be implemented - will we have something like ComplieLessToCssAndCompressProcessor or will be able to apply two processors (CompileLessProcessor and CompressCssProcessor)?

There will be a separate processors for compile, process, combine.