Modernizing Your WordPress Workflow – with Grunt & Bower – Common Problems for Theme Developers



Modernizing Your WordPress Workflow – with Grunt & Bower – Common Problems for Theme Developers

0 0


wordcamp2014

WordCamp 2014

On Github TheRealAlan / wordcamp2014

Modernizing Your WordPress Workflow

with Grunt & Bower

There are a ton of new tools that can help us be more productive and can make our projects more fun. Using Bower to keep our plugins up to date and Grunt for task management, we can spend more time on what's important - building great websites and apps.

What We'll Cover

  • Getting used to the terminal
  • Managing project plugins & frameworks with Bower
  • Creating tasks in Grunt that will process our CSS, minify and concatenate our JavaScript, optimize images, and refresh our browser instantly
Grunt and Bower exist as toolkits to solve common problems for developers.

Common Problems for Theme Developers

  • HTML / Template Management
  • CSS Management
  • JavaScript Management
  • CSS / JavaScript Concatenation / Minification
  • Image Optimization
  • Live Browser Updating
  • Local Server Environment

Tools to Help with These Problems

  • HAML, JADE, SLIM, Markdown
  • LESS, SASS, Stylus
  • CoffeeScript, LiveScript
  • WordPress Plugins, GUI Apps - CodeKit, Koala, Hammer
  • MAMP, LAMP, XAMPP, AMPPS, Vagrant

Problems Bower Solves

JavaScript Plugin / Framework Management & Updating

Problems Grunt Solves

All the Rest

Grunt Advantages Over GUIs

  • Portable with Project
  • Configurable for Multiple Environments (dev, dist)
  • Every Detail is Customizable
  • It's Free

Folder Structure

html             // your public folder
assets           // the files you will be editing
└─ less
└─ js
└─ img
bower_components // home to your Bower packages
node_modules     // home to your Node packages
tmp              // holds concatenated js to lint
bower.json       // the list of your Bower packages
package.json     // the list of your Node packages
Gruntfile.js     // where the Grunt magic happens
.jshintrc        // your JS Hint config file (optional)
					
This layout is for custom themes. Developers building themes for resale will want to move their Bower and Grunt files into the theme folder itself.

Terminal Setup for Fun++

Get iTerm2. Install OhMyZsh with
curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh
							
Install Homebrew with
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)
							
Get Homebrew up to date and clean by running brew update and brew doctor, then add it to the path with
export PATH="/usr/local/bin:$PATH" >> ~/.bash_profile
							
Install Node.js with brew install node Install the Grunt CLI with npm install -g grunt-cli Install Bower with npm install -g bower This assumes you're on a Mac. Homebrew requires xCode to be installed first. If prompted to install Xcode Command Line Tools, choose Yes.

Bower Packages

Create a file called bower.json.

{
  "name": "your-project-name",
  "version": "1.0.0",
  "dependencies": {
    
  }
}
						

Node (Grunt) Packages

Create a file called package.json.

{
  "name": "your-project",
  "version": "1.0.0",
  "dependencies": {

  }
}
						

Grunt Configuration

Create a file called Gruntfile.js.

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

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

    // package options will go here

  });

  // register tasks here
  
  grunt.registerTask('default', [
    
    // default actions go here
		
  ]);

};
						
With this file created, Grunt is up and running. You'll execute it by going to the project directory in the terminal and typing 'grunt'. But, in its current state, it won't actually do anything. This sample configuration doesn't have any packages loaded. Before we load any packages, we need to know what we want Grunt to do for us.

Packages

Get the Bower packages you want from their registry.

First think about what you will always need for your project. What frameworks do you use? Bootstrap? Foundation? Font Awesome? jQuery? Zepto? Round these up with Bower (don't forget about the package directory [here](http://sindresorhus.com/bower-components/)). Do you use LESS or SASS? Get those files set up. Then depending on what you need out of these frameworks, you'll configure Grunt to process your CSS, lint and concatenate your JavaScript, and minify both.

CSS Preprocessing

You'll want the LESS, SASS, or some other package if your CSS preference is different.

npm install grunt-contrib-less --save-dev
						

or

npm install grunt-contrib-compass --save-dev
						

JavaScript Linting

Get JSLint.

npm install grunt-jslint --save-dev
						

JavaScript File Concatenation

You'll want this.

npm install grunt-contrib-concat --save-dev
						

JavaScript Minification

Get Uglify.

npm install grunt-contrib-uglify --save-dev
						

Error Notifications

Try Grunt Notify.

npm install grunt-notify --save-dev
						

Image Optimization

I like Imagemin.

npm install grunt-contrib-imagemin --save-dev
						

Live Updating

You want to use Watch. For updating CSS and JavaScript in the browser without refreshing the page, get the Chrome extension LiveReload.

npm install grunt-contrib-watch --save-dev
						

Server

Want to set up a node.js server for your project and ditch MAMP? Get Express.

For WordPress, you'll want the PHP version.
Go ahead and follow the instructions for setting up each of these that you want. Don't worry about configuring them yet, this can be confusing at first and I have a sample I'll show at the end to get you started.

With the packages you want registered, the 'Load Tasks' section of your file should look something like this:

// Load tasks
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-imagemin');
grunt.loadNpmTasks('grunt-contrib-compass');
					

Configuring Options

// package options
jshint: {
  options: {
    jshintrc: '.jshintrc' // jshint config file
  },
  all: [
    'Gruntfile.js',
    'assets/js/*.js'
  ]
},
concat: {
  basic: {
    src: [
      'bower_components/jquery/dist/jquery.js',
      'bower_components/foundation/js/foundation/foundation.js',
      'assets/js/main.js'
    ],
    dest: 'tmp/app.js'
  },
  extras: {
    src: [
      'bower_components/modernizr/modernizr.js'
    ],
    dest: 'tmp/modernizr.js'
  }
},
compass: {
  dist: {
    options: {
      config: 'config.rb'
    }
  }
},
imagemin: {
  dynamic: {
    files: [{
      expand: true,
      cwd: 'assets/img/',
      src: ['**/*.{png,jpg,gif}'],
      dest: 'public/build/img/'
    }]
  }
},
uglify: {
  build: {
    files: {
      'public/build/js/modernizr.min.js' : 'tmp/modernizr.js',
      'public/build/js/app.min.js' : 'tmp/app.js'
    }
  }
},
clean: {
  dist: [
    'tmp/**',
    'public/build/img/**'
  ]
},
					
Next you'll want to set the options (under the comment 'package options' in the sample file) for each task that will be run. A few things to note here - I'm using a 'tmp' folder to concatenate the JavaScript to, before it's minified. I also have my 'production' files in an `assets` folder outside of the public folder, and everything is processed into a `build` folder inside my public folder. You don't have to do this, but I prefer to keep my source files and processed files separate. Also, watch your commas. As you nest options, it's easy to miss one.

Watch

A sample configuration:

watch: {
  compass: {
    files: ['assets/sass/**/*.{scss,sass}'],
    tasks: ['compass']
  },
  css: {
    files: ['public/build/css/*'],
    options: {
      livereload: true
    }
  },
  js: {
    files: [
      'assets/js/*.js'
    ],
    tasks: ['concat', 'uglify'],
    options: {
      livereload: true,
      atBegin: true
    }
  },
  imagemin: {
    files: [
      'assets/img/**'
    ],
    tasks: ['imagemin'],
    options: {
      livereload: true,
      atBegin: true
    }
  }
}
					
'Watch' is a very powerful Grunt package that continues to run until you tell it to stop. This is super useful for development tasks like rebuilding your files as you make changes and refreshing the browser. What's happening here is for as long as Grunt Watch is running, it's watching all of our production files and re-running the tasks we tell it to whenever one of those files changes. This can be run with the command `grunt watch`, but there's a simpler way we can incorporate this by including it in our default tasks.

Register Default Tasks

// Register default tasks
grunt.registerTask('default', [
  'watch'
]);
					
Any tasks inside 'default' will be run whenever you run the command 'grunt'. This configuration runs Grunt's 'watch' function. With this, all you need to type to get everything up and running is the simple command 'grunt'.

Putting it All Together

With all of these modules registered and configured, your Gruntfile.js should look something like this:

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

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

    // package options
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        'tmp/js/*.js'
      ]
    },
    concat: {
      basic: {
        src: [
          'bower_components/jquery/dist/jquery.js',
          'bower_components/foundation/js/foundation/foundation.js',
          'assets/js/main.js'
        ],
        dest: 'tmp/app.js'
      },
      extras: {
        src: [
          'bower_components/modernizr/modernizr.js'
        ],
        dest: 'tmp/modernizr.js'
      }
    },
    compass: {
      dist: {
        options: {
          config: 'config.rb'
        }
      }
    },
    imagemin: {
      dynamic: {
        files: [{
          expand: true,
          cwd: 'assets/img/',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'public/build/img/'
        }]
      }
    },
    uglify: {
      build: {
        files: {
          'public/build/js/modernizr.min.js' : 'tmp/modernizr.js',
          'public/build/js/app.min.js' : 'tmp/app.js'
        }
      }
    },
    clean: {
      dist: [
        'tmp/**',
        'public/build/img/**'
      ]
    },
    watch: {
      compass: {
        files: ['assets/sass/**/*.{scss,sass}'],
        tasks: ['compass']
      },
      css: {
        files: ['public/build/css/*'],
        options: {
          livereload: true
        }
      },
      js: {
        files: [
          'assets/js/*.js'
        ],
        tasks: ['concat', 'uglify'],
        options: {
          livereload: true,
          atBegin: true
        }
      },
      imagemin: {
        files: [
          'assets/img/**'
        ],
        tasks: ['imagemin'],
        options: {
          livereload: true,
          atBegin: true
        }
      }
    }
  });

  // Load tasks
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-notify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-imagemin');
  grunt.loadNpmTasks('grunt-contrib-compass');

  // Register default tasks
  grunt.registerTask('default', [
    'watch'
  ]);

};
					

Go Play

Hopefully this is enough to get you started using Grunt to handle your common tasks and Bower to handle your frameworks and plugins. Grunt is as complex as you want to make it - there is no end to how much you can make it do for you and I've only scratched the surface.

Follow Me

Twitter

@alancrissey

GitHub

TheRealAlan

CodePen

TheRealAlan

Website

alancrissey.com