How I use Bower and Grunt with my Laravel projects

I just ran across an excellent article on how to use Grunt with Laravel, and I thought I’d share my process. First things first… install node, npm, bower, grunt, and all that. Google it if this is new to you.

With Laravel installed, I actually use the Laravel-Assetic package for all my concatenation and minimization. That way, cacheing is taken care of automatically. Here’s my composer,json file with all the needed filters

"require": {
		"laravel/framework": "4.0.*",
		"slushie/laravel-assetic": "dev-master",
		"leafo/lessphp": "dev-master",
		"lmammino/jsmin4assetic": "1.0.*",
		"natxet/CssMin": "dev-master"
},

Next, I use Bower for all my asset dependencies. To specify where the files are saved, I needed to add a .bowerrc file in my app’s root, and added this

{
	"directory": "assets/bower"
}

I add the files to use in my bower.json file

{
  "name": "Matula",
  "version": "0.0.0",
  "homepage": "https://terrymatula.com",
  "authors": [
    "Matula <terrymatula@gmail.com>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "underscore": "~1.4.4",
    "Eventable": "~1.0.1",
    "jquery": "~2.0.3",
    "bootstrap": "jasny/bootstrap#~3.0.1-p7",
    "sir-trevor-js": "~0.3.0",
    "jquery-ui": "~1.10.3",
    "angular": "~1.2.2"
  },
  "resolutions": {
    "jquery": "~2.0.3"
  }
}

In the command line, run `bower install` and all those files are added to the ‘assets/bower’ directory. Then (after following the Laravel-Assetic install instructions) we need to add the files to use in the Laravel-Assetic config file. Here’s an example of my config:

<?php
/**
 * Configure laravel-assetic options in this file.
 *
 * @package slushie/laravel-assetic
 */

return array(
  'groups' => array(
    // Javascripts
    'script' => array(
      'filters' => array(
        'js_min'
      ),
      'assets' => array(
        'underscore',
        'eventable',
        'jquery',
        'jqueryui',
        'redactorjs',
        'bootstrapjs',
        'sirtrevorjs',
        'sirtrevorcustomjs',
        'customjs'
      ),
      'output' => 'js/matula.js'
    ),

    // CSS
    'style' => array(
      'filters' => array(
        'css_min'
      ),
      'assets' => array(
        'bootstrapcss',
        'sirtrevoricons',
        'sirtrevorcss',
        'redactorcss',
        'customcss'
      ),
      'output' => 'css/matula.css'
    ),

    // LESS files
    'less' => array(
      // Convert LESS to css
      'filters' => array(
        'less',
        'css_min'
      ),
      'assets' => array(
        public_path('less/style.less'),
      ),
      'output' => 'css/less.css'
    ),
  ),

  'filters' => array(
    // Filters
    'js_min'  => 'Assetic\Filter\JsMinFilter',
    'css_min' => 'Assetic\Filter\CssMinFilter',
    'less'    => 'Assetic\Filter\LessphpFilter'
  ),

  // List of Assets to Use
  'assets' => array(
    // Add name to assets
    'jquery'            => public_path('bower/jquery/jquery.js'),
    'angular'           => public_path('bower/angular/angular.js'),
    'bootstrapjs'       => public_path('bower/bootstrap/dist/js/bootstrap.js'),
    'eventable'         => public_path('bower/Eventable/eventable.js'),
    'jqueryui'          => public_path('bower/jquery-ui/ui/jquery-ui.js'),
    'jqueryfileapi'     => public_path('bower/jquery.fileapi/jquery.fileapi.js'),
    'sirtrevorjs'       => public_path('bower/sir-trevor-js/sir-trevor.js'),
    'underscore'        => public_path('bower/underscore/underscore.js'),
    'sirtrevorcustomjs' => public_path('js/custom/sir-trevor-custom-blocks.js'),
    'redactorjs'        => public_path('js/custom/redactor.js'),
    'customjs'          => public_path('js/custom/bs.js'),

    'bootstrapcss'      => public_path('bower/bootstrap/dist/css/bootstrap.css'),
    'sirtrevoricons'    => public_path('bower/sir-trevor-js/sir-trevor-icons.css'),
    'sirtrevorcss'      => public_path('bower/sir-trevor-js/sir-trevor.css'),
    'redactorcss'       => public_path('css/custom/redactor.css'),
    'customcss'         => public_path('css/custom/custom.css'),
    'customless'        => public_path('less/*')
  )
);

Some explanation: Starting at the bottom, we have our ‘assets’ array, where we assign a name to each asset and point it to the file we want to use. Then, we have our ‘filters’ array where we give a name to each filter we’re using. The composer file loaded in the filters we’re using. Before that, we create our ‘groups’ array. Each group is basically the filters we want to use, the files we want to filter, and then the output file to create. The order of the files is important, since this will affect any dependent files (eg, jquery needs to be before jquery-ui).

Now, we can run `php artisan asset:warm’ and all our files will be created. The one issue I had during development was that I was doing lots of quick updates to the css/js, and it would take time for the asset cache to clear. So I had to ‘warm’ them often. This was a time waste, so I’m using grunt to take care of that for me, ad live-reload it as well.

The only 2 grunt packages we need are ‘grunt-contrib-watch’ and ‘grunt-exec’. This is how my Gruntfile.js file looks:

module.exports = function(grunt) {

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

    exec: {
      warmscript: {
        cmd: 'php artisan asset:warm script'
      },
      warmstyle: {
        cmd: 'php artisan asset:warm style'
      },
      warmless: {
        cmd: 'php artisan asset:warm less'
      }
    },
    watch: {
      options: {
        livereload: true
      },
      less: {
        files: ['assets/less/*.less'],
        tasks: ['exec:warmless']
      },
      css: {
        files: ['assets/css/custom/*.css'],
        tasks: ['exec:warmstyle']
      },
      js: {
        files: ['assets/js/custom/*.js'],
        tasks: ['exec:warmscript']
      },
      html: {
        files: ['app/views/*.php', 'app/views/**/*.php']
      }
    }
  });

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

  grunt.registerTask('default', ['exec', 'watch']);

};

I open a dedicated command line tab, run `grunt` and it will warm everything on start. Then, anytime I make a change to one of the files, it’s automatically warmed and the page is reloaded. The ‘html’ part will reload it when my views are changed.

One word of warning… the way I have it set up, it loads every js/css into every view. While it’s handy to only have 2 (or 3 in my case) minimized files that need to be loaded, we’ll only need Redactor on the pages with forms.  It might be best to create our groups dynamically, and only use the assets we need for each view.

Leave a Reply