As you have read in our previous article (if not, do so: A solid and automated front-end development workflow), we have done a lot to optimize our front-end workflow. We are now using Grunt to automate our recurring tasks and Bower as our front-end package manager. In this article we will give you insight into how this is implemented in a Symfony project.

In this article:

How did we implement Grunt?

The Grunt-files can be found in the root of the project. This way it is easy to execute the grunt-commands next to the symfony one's. For Grunt, you'll need the following files:

  • package.json
  • Gruntfile.js

package.json

The pakage.json contains all of the dependencies needed in your Grunt workflow. After setting your dependencies, you can install them simply by running 'npm install'.

{
  "name": "YourProjectName",
  "version": "0.0.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "bower": "latest",
    ...
  }
}

Gruntfile.js

The Gruntfile.js is used to configure the task runner itself as well as the tasks of your choice. We will show you some details of this for scss compilation, watching and injecting in the last section of this article. You can always watch our complete Gruntfile.js in the KunstmaanGeneratorBundle.


How did we implement Bower?

The Bower-files can also be found in the root of the project. For Bower, you'll need the following files:

  • .bowerrc
  • bower.json

.bowerrc

This is a hidden file that can be used to choose where you want your vendors to be installed. We have chosen to install the vendors in "web/vendor/".

{
  "directory": "web/vendor/"
}

bower.json

The bower.json file contains all of the vendors you want to download and manage trough Bower. After you added the required vendors, you can install them simply by running 'bower install'.

{
  "name": "YourProjectName",
  "version": "0.0.0",
  "dependencies": {
    "bourbon": "latest",
    "modularized-normalize-scss": "latest",
    ...
  }
}

Detailed example: compiling, watching and injecting scss with Grunt

We shall now take a closer look at how we used Grunt for watching and compiling our scss to css and then injecting these changes into the browser without a pagerefresh.

Location of the scss files

The scss folder is placed in the public folder under Resources. 

Compiling our scss to css

For the compilation of scss to css we use the grunt-sass task. This task uses the experimental and superfast Node.js based Sass compiler node-sass (which only compiles .scss files). Note that node-sass is currently under heavy development and might be unstable. Check out grunt-contrib-sass (based on Ruby Sass) if you want something stable that also supports the old syntax, but which is in turn, much slower.

package.json

First, you need to configure that dependency in the package.json.

{
  "name": "YourProjectName",
  "version": "0.0.0",
  "devDependencies": {
    ...
    "grunt-sass": "latest",
    ...
  }
}
Gruntfile.js

After setting the grunt-sass dependency and installing it ('npm install'), we can now setup the sass-task in our Gruntfile. We have chosen to put the compiled css in 'web/frontend/'.

module.exports = function (grunt) {
    "use strict";

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        sass: {
            dist: {
                options: {
                    outputStyle: 'compressed'
                },
                files: {
                    'web/frontend/style.css': 'src/Path/To/Resources/public/scss/style.scss'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-sass');

    grunt.registerTask('build', 'sass');
};

Reference the css in Twig

We use Twig as our template engine. The build css is included as such:

{% stylesheets
    'frontend/style.css'
%}
   <link rel='stylesheet' href='{{ asset_url }}' type='text/css' />
{% endstylesheets %}

Watching scss

Now that we can compile our scss to css, we can also automate that proces. We will watch for changes in our scss files and if so, compile it to a new css. We do this with the help of the grunt-contrib-watch task.

package.json

Lets configure that dependency in the package.json.

{
  "name": "YourProjectName",
  "version": "0.0.0",
  "devDependencies": {
    ...
    "grunt-sass": "latest",
    "grunt-contrib-watch": "latest",
    ...
  }
}
Gruntfile.js

Now we can setup the watch-task in our Gruntfile. 

module.exports = function (grunt) {
    "use strict";

    var YourBundleName;

    YourBundleName = {
        'scss': 'src/Path/To/Resources/public/scss/**/*.scss'
    };

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        watch: {
            YourBundleNameScss: {
                files: YourBundleName.scss,
                tasks: 'sass'
            }
        },

        sass: {
            dist: {
                options: {
                    outputStyle: 'compressed'
                },
                files: {
                    'web/frontend/style.css': 'src/Path/To/Resources/public/scss/style.scss'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-sass');

    grunt.registerTask('default', 'watch');
    grunt.registerTask('build', 'sass');
};

Inject the changes without a page-reload using livereload

In the grunt-contrib-watch task, there is also a livereload option available. This will start a live reload server with the watch task per target. Then after the indicated tasks have ran, the live reload server will be triggered with the modified files.

KunstmaanLiveReloadBundle

To get the livereload option working in our Symfony project, we've build the KunstmaanLiveReloadBundle. This bundle injects a livereload snippet in each of our pages while in the dev environment. The watch task can hook into this and push changes to the page. 

Gruntfile.js

Setup the livereload option in the watch-task. Be sure you target the compiled css and not the scss files. If you target the scss files, you will have a page-refresh and not a live-injection.

module.exports = function (grunt) {
    "use strict";

    var YourBundleName;

    YourBundleName = {
        'scss': 'src/Path/To/Resources/public/scss/**/*.scss'
    };

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        watch: {
            YourBundleNameScss: {
                files: YourBundleName.scss,
                tasks: 'sass'
            },
            livereload: {
                files: 'web/frontend/style.css',
                options: {
                    livereload: true
                }
            }
        },

        sass: {
            dist: {
                options: {
                    outputStyle: 'compressed'
                },
                files: {
                    'web/frontend/style.css': 'src/Path/To/Resources/public/scss/style.scss'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-sass');

    grunt.registerTask('default', 'watch');
    grunt.registerTask('build', 'sass');
};

This was a detailed example for how we implemented Grunt in a Symfony project for compiling, watching and injecting scss/css. Of course we use Grunt for a lot more than that. You can always watch our complete Gruntfile.js in the KunstmaanGeneratorBundle to get a complete overview. Questions or remarks? Please do so below. Thanks for reading.