Bye bye assetic, welcome grunt


The goal of this post is to show you how to use grunt-usemin in a symfony project. 

Why should you use grunt ?

Assetic is tightly coupled to symfony framework. Some people can say that it's normal because symfony is a full stack framework. When compiling php code if there is any error, the assetic:dump command will not work. Dealing with css and js files shouldn't have anything to do with the php code. Assetic can become very slow on large project with multiple js and css files. Using assetic is less productive than using grunt. Let's see how to add grunt in a symfony project.

 

With assetic, your twig template look like this : 


{# the following css and js files are located in web directory of symfony #}
{# css #}
{% stylesheets  filter='cssrewrite, less, ?yui_css' output='css/compiled/monixo_index.css'
'css/main.css'
'css/font-awesome.min.css'
'css/common.less'
%}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}

{# js #}
{% javascripts output='js/compiled/monixo_js.js' filter='?yui_js'
'js/bootstrap.min.js'
'js/bootstrap/tooltip.js'
'js/bootstrap/popover.js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}

After replacing assetic by grunt we will have this


<!-- build:css(web) /assets/built.css -->
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/font-awesome.min.css" />
<link rel="stylesheet" href="/css/common.css" />
<!-- endbuild -->

<!-- build:js(web) /assets/built.js -->
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/bootstrap/tooltip.js"></script>
<script type="text/javascript" src="/js/bootstrap/popover.js"></script>
<!-- endbuild --> 

Let's go ! 


First we are going to install all required tools. Copy the following package.json file in your symfony project root directory.


{
  "name": "My project",
  "namelower": "MP",
  "version": "0.0.1",
  "description": "Project",
  "readme": "README.md",
  "dependencies": {
    "express": "~3.4.7",
    "grunt-usemin": "^3.0.0"
  },
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-less": "^1.0.1",
    "grunt-contrib-concat": "^0.5.1",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-contrib-cssmin": "~0.9.0",
    "grunt-open": "~0.2.3",
    "time-grunt": "~0.3.1",
    "grunt-usemin": "3.0.0",
    "grunt-concurrent": "~0.5.0",
    "load-grunt-tasks": "~0.4.0",
    "grunt-contrib-uglify": "^0.9.1",
    "grunt-cli": "^0.1.13"
  },
  "engines": {
    "node": ">=0.10.0"
  }
}

Run the following command

npm install

Now every required tool is installed, create the Gruntfile.js in your project root directory


module.exports = function(grunt) {
    require('load-grunt-tasks')(grunt);
    grunt.initConfig({
        dirs: {
            filesToParse: ['app/**/*.twig', 'src/**/*.twig'],//here we list all files that are going to be parsed by grunt for concatenation and minification. In this example we parse all files located in the app and src directory of symfony
        },
        useminPrepare: {
            html: '<%= dirs.filesToParse %>',
            options: {
                dest: 'web',
                flow: {
                    steps: {
                        'js': ['uglifyjs'],
                        'css': ['cssmin'],
                    },
                    post: {}
                }
            }
        },

        usemin: {
            html: '<%= dirs.filesToParse %>',
        },

        cssmin: {
            options: {
                root: 'web' // we set the root web path in order to replace relative paths for static resources (css files) with absolute path
            }
        },

        less: {
            server: {
                files: {//we list all less files that are going to be compiled to css
                    "web/css/common.css": "web/css/common.less"
                }
            }
        },

        //we watch less files changes and compile them automatically
        watch : {
            scripts : {
                files : [
                    'web/css/**/*.less',
                ],
                tasks : [ 'less' ],
                options : {
                    spawn : false
                },
            },
        },

    });

    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-usemin');
    grunt.registerTask('dev', [
        'less'
    ]);
    grunt.registerTask('default', [
        'less',
        'useminPrepare',
        'cssmin',
        'uglify',
        'usemin',
    ]);

};

Now in any of your twig templates located in src or app folder remplace stylesheet and javascript block by the following


<!-- build:css(web) /assets/built.css -->
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/font-awesome.min.css" />
<link rel="stylesheet" href="/css/common.css" />
<!-- endbuild -->

<!-- build:js(web) /assets/built.js -->
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/bootstrap/tooltip.js"></script>
<script type="text/javascript" src="/js/bootstrap/popover.js"></script>
<!-- endbuild --> 

In development environment everything works fine, there is no need to concatenate or to minify anything. In production, you have to run from your project root directory this command : grunt . After running the grunt command you will have this result in your template


<link rel="stylesheet" href="/assets/built.css" />
<script type="text/javascript" src="/assets/built.js"></script>

We have one final concatenated and minified css file and js file. built.css and built.js file are created in web/assets directory. Everything is now ready for deployment in production 

If you need more information about grunt-usemin, look at the documentation here : https://github.com/yeoman/grunt-usemin

(Do not hesitate to post comments if you need help !)



Amady, 13/08/2015