title: frontend workflow (Grunt) meet php backend (Symfony2-Twig)
body: <p>I'm the front-end guy, I use Grunt (cssmin, less compilation, uglify, livereload, image minify, watch, lint...) and I started working with a back-end guy that uses Symfony (twig templating, assetics for most of the task I do with Grunt...)</p>
  
  <p>We are using a sort of Model-View-ViewModel pattern (I'm not back-end expert sorry).</p>
  
  <p>The problem is to find the right workflow for both of us.
  <br>I'd like to stay with Grunt since it's the best for front-end right now I think.</p>
  
  <p>My usual workflow is to fire-up Grunt webserver at localhost:9000 and coding with livereload, istant less compilation.
  <br>When I'm done I can use "Grunt build" to do the "production" tasks (minify, uglify, image compression...)</p>
  
  <p>The problem comes with Php and Twig templating, I can't use grunt and fire-up a webserver for Php and I can't render the templates with twig.js because it's not what we are using in production.
  <br>Also we have to deal with Symfony2 framework server-side.</p>
  
  <p>For the livereloading part I'm using the stand-alone plugin but monitors files and reload the page but for example the less compilation is not working since Lessphp is not fully compatible with Twitter Bootstrap (the framework I'm using on front-end)</p>
  
  <p>Do you have any suggestion? I can add details in case.</p>
  
  <p>I'd prefer to stay with Grunt as much as possible but I can think about using Assetics if he does all the same task or other solution.</p>
  
  <p>This is my actual Gruntfile.js when I work only on front-end:</p>
  
  <pre><code>var LIVERELOAD_PORT = 35729;
  var lrSnippet = require('connect-livereload')({
      port: LIVERELOAD_PORT
  });
  var mountFolder = function(connect, dir) {
      return connect.static(require('path').resolve(dir));
  };
  
  module.exports = function(grunt) {    
      pkg: grunt.file.readJSON('package.json'),
  
                  src: 'src',
          app: 'app',
          assets: '&lt;%= project.app %&gt;/assets',
          css: [
              '&lt;%= project.src %&gt;/less/bootstrap.less'
          ],
          js: [
              '&lt;%= project.src %&gt;/js/*.js'
          ]
      },
  
      connect: {
          options: {
              port: 9000,
              hostname: '*'
          },
          livereload: {
              options: {
                  middleware: function(connect) {
                      return [lrSnippet, mountFolder(connect, 'app')];
                  }
              }
          }
      },
  
      concat: {
          dev: {
              files: {
                  '&lt;%= project.assets %&gt;/js/scripts.min.js': '&lt;%= project.js %&gt;'
              }
          },
          options: {
              stripBanners: true,
              nonull: true,
              banner: '&lt;%= tag.banner %&gt;'
          }
      },
  
      uglify: {
          options: {
              banner: "&lt;%= tag.banner %&gt;"
          },
          dist: {
              files: {
                  '&lt;%= project.assets %&gt;/js/scripts.min.js': '&lt;%= project.js %&gt;'
              }
          }
      },
  
      less: {
          dev: {
              files: {
                  '&lt;%= project.assets %&gt;/css/style.min.css': '&lt;%= project.css %&gt;'
              }
          },
          dist: {
              options: {
                  cleancss: true
              },
              files: {
                  '&lt;%= project.assets %&gt;/css/style.min.css': '&lt;%= project.css %&gt;'
              }
          }
      },
  
      imagemin: { // Task
          dynamic: { // Another target
              files: [{
                  expand: true, // Enable dynamic expansion
                  cwd: '&lt;%= project.src %&gt;/img', // Src matches are relative to this path
                  src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
                  dest: '&lt;%= project.assets %&gt;/img' // Destination path prefix
              }]
          }
      },
  
      open: {
          server: {
              path: 'http://localhost:&lt;%= connect.options.port %&gt;'
          }
      },
  
      watch: {
          concat: {
              files: '&lt;%= project.src %&gt;/js/{,*/}*.js',
              tasks: ['concat:dev', 'jshint']
          },
          less: {
              files: '&lt;%= project.src %&gt;/less/{,*/}*.less',
              tasks: ['less:dev']
          },
          livereload: {
              options: {
                  livereload: LIVERELOAD_PORT
              },
              files: [
                  '&lt;%= project.app %&gt;/{,*/}*.html',
                  '&lt;%= project.assets %&gt;/css/*.css',
                  '&lt;%= project.assets %&gt;/js/{,*/}*.js',
                  '&lt;%= project.assets %&gt;/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
              ]
          }
      }
  };
  
  grunt.registerTask('default', [
      'less:dev',
      'concat:dev',
      'connect:livereload',
      'open',
      'watch'
  ]);
  
  grunt.registerTask('build', [
      'less:dist',
      'uglify',
      'imagemin'
  ]);
  </code></pre>