We've been meaning to publish some more technical, web-oriented blog posts for some time but, as always, client work comes first. Recently however, we've been joined by our new guy Hamish, and this has afforded us time to develop some new workflows that we are excited to share. In this, the first in a series of posts about 'how we do stuff at Toward', Hamish is going to take a look at how Grunt (the 'Javascript task runner' — gruntjs.com) is making our workflow more efficient and, crucially, a bit less tedious. Exciting stuff!
So what is this 'Grunt' thing?
Well, Grunt is a command-line based tool built on top of Node.js (nodejs.org) that allows us to automate a bunch of stuff that we do on a regular basis — from compiling code to moving databases between servers. It achieves this via 'tasks', configurable via a Javascript file called 'gruntfile.js' that is placed in your project file structure. Tasks are provided by 'plugins' — more on this later in this post.
If you aren't a developer, it may come as a surprise that web development isn't all supermodels and fast cars — there's some pretty boring, repetitive stuff we have to do, too. Some of which is pretty time-consuming — this is where Grunt comes in. If you are a developer and haven't used Grunt before (where have you been, man?), you're in for a treat.
There's a ton of blog posts out there already that provide an introduction to Grunt and how to install it and so forth so I won't go over that here — I recommend you checkout the projects 'getting started' page (gruntjs.com/getting-started) for that information. Furthermore, most introductions to Grunt will cover how to use it to compile SASS into CSS and the like so I'll skip over that and look at some more specific stuff that we have been using it for. Consequently, this post will assume some familiarity with Grunt should you wish to use any of the information provided. Feel free to ask us questions on Twitter should you have any problems.
Deploy databases, automagically
Firstly, one of my least favourite tasks that was ripe for automation is hauling databases to and from servers. Here at Toward we use Wordpress for the vast majority of our client work and, as anyone who is familiar with Wordpress will know, migrating the database from your local development environment to a server necessitates replacing all instances of the site URL in the database (e.g. from 'toward.local' to 'toward.studio'). A typical workflow here would be to:
- fire up phpMyAdmin on your machine.
- export the database.
- use some tool (maybe SQL) to 'search and replace' the respective URLs.
- launch phpMyAdmin on your development server.
- import the database.
As you can imagine, this is pretty tedious stuff to do on a regular basis. Thankfully, Grunt can help in this regard and reduce this workflow to a single command. Awesome.
One of the great things about Grunt is the amount of community plugins that exist to make life easier for everyone. A simple Google search for "grunt [thingyouwantodo]" will very likely provide a solution if one exists, so finding a task for our database deployments problem was easy and unearthed the excellent 'grunt-deployments' (github.com/getdave/grunt-deployments). According to the introduction for this plugin, grunt-deployments will:
Push/pull MYSQL databases from one location to another using Grunt. Designed to ease the pain of migrating databases from one environment (local) to another environment (remotes). Automatically updates hardcoded siteurl references and backs up source and target before any modifications are made.
Perfect. So let's look at how this is done — this is an example snippet from our 'gruntfile.js':
module.exports = function(grunt) { var path = require('path'); // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), /** * Task for copying databases between hosts with ssh */ deployments: { options: { backup_dir: "db_backups" }, local: { title: "Local", database: "local_database_name", user: "local_database_user", pass: "y0ur_pa55w0rd", host: "localhost", url: "website.dev" }, staging: { title: "Staging", database: "staging_database_name", user: "staging_database_user", pass: "y0ur_pa55w0rd", host: "localhost", url: "staging.website.com", ssh_host: "root@yourdevserver.com" }, live: { title: "Live", database: "live_database_name", user: "live_database_user", pass: "y0ur_pa55w0rd", host: "localhost", url: "website.com", ssh_host: "root@yourliveserver.com" } } }); grunt.loadNpmTasks('grunt-deployments'); // other tasks go here... };
As you can see, we have three environments configured here – local, staging and live. Deploying the database from local to live is as easy as entering this command in to Terminal:
grunt db_push --target="live"
This will:
- connect to the server via ssh
- use 'mysqldump' to export the live database
- back it up to your local environment
- mysqldump your local database
- perform the aforementioned search and replace on the relevant URLs in the database
- import the database in to your live environment
Amazing, huh? Not only have you deployed the database but also created backups of both should anything go wrong. Furthermore, if you want to pull your database down from your staging server you can do this:
grunt db_pull --target="staging"
More databases
We can take this further by using Grunt to create the databases in the first place. For this, we use the 'grunt-bg-shell' plugin (github.com/rma4ok/grunt-bg-shell) which allows us to execute shell commands from our Grunt tasks. This is the config in our 'gruntfile.js':
bgShell: { createLocalDb: { cmd: "mysql --host=localhost -uroot -proot -e \"create database <%= deployments.local.database %>; GRANT ALL PRIVILEGES ON <%= deployments.local.database %>.* TO <%= deployments.local.user %>@localhost IDENTIFIED BY '<%= deployments.local.pass %>'\" " } } grunt.loadNpmTasks('grunt-bg-shell'); grunt.registerTask("createLocalDb", 'bgShell:createLocalDb');
This works by hooking in to the configuration values of the 'deployments' task and using those to create the local database. Taking this further, we could create tasks to ssh in to the staging and live servers and create the relevant databases there. Furthermore (more on this in a later post) this can be part of project 'scaffolding' tasks that automate the initial set-up of a project. Currently, we do this via 'grunt-init' (github.com/gruntjs/grunt-init) but this project has deprecated in favour of Yeoman (yeoman.io) and so we will be moving on to that, perhaps with a post to accompany the new workflow.
SVG fallback, made (relatively) simple
Firstly, much of the workflow described here can be achieved via the excellent Grunticon (github.com/filamentgroup/grunticon) project. However, I found the way it worked a bit too opinionated and wanted to create a workflow to suit my own way of doing things. My needs were simple: automate creating PNG fallback for SVG files. I've tried various approaches to handling SVG and recently settled on a way of doing things — basically, I create a 'vector.ai' Illustrator file with all artwork separated on to individual artboards. From there, I can export each asset in to its' own file via the 'use artboards' option when you save. Unfortunately, Illustrator prefixes each file with "vector_" and I haven't found a way of preventing this — so I use Grunt to batch rename the files via the catchilly-titled 'grunt-file-regex-name' (github.com/kashiif/grunt-file-regex-rename).
Once that is sorted, we can then use 'grunt-svg2png' (github.com/dbushell/grunt-svg2png) to convert each SVG to a PNG, saving us some seriously tedious clicking. Woot! Here are both tasks:
// task configuration /** * Converts SVG to PNG */ svg2png: { all: { files: [ { src: 'images/**/*.svg' } ] } }, /** * Renames files */ fileregexrename: { dist: { files: { "/images/vectors/**": "/images/vectors/*" }, options: { replacements: [{ pattern: "vectors_", replacement: "" }] } } } // elsewhere, register the task grunt.registerTask('svg', ['fileregexrename', 'svg2png:all']);
From the command-line we run:
grunt svg
...and Grunt will run both tasks, first renaming and then creating PNGs of all the images. Sweet!
What next?
These are just a few examples of what Grunt can achieve and we will look at how it fits into our broader workflow, including integration with technologies such as Git, Bower and Capistrano in a future blog post. Until then, feel free to drop us a tweet with any feedback or insights into how you do things.