About The Author

Mike Cunsolo is Growth Marketing Manager at CDNify, a content delivery network for Developers and Start Ups. More about Mike

Get Up And Running With Grunt

Quick Summary

In this article, we’ll explore how to use Grunt in a project to speed up and change the way you develop websites. We’ll look briefly at what Grunt can do, before jumping into how to set up and use its various plugins to do all of the heavy lifting in a project.

Grunt.js

We’ll then look at how to build a simple input validator, using Sass as a preprocessor, how to use grunt-cssc and CssMin to combine and minify our CSS, how to use HTMLHint to make sure our HTML is written correctly, and how to build our compressed assets on the fly. Lastly, we’ll look at using UglifyJS to reduce the size of our JavaScript and ensure that our website uses as little bandwidth as possible.

Table of Contents

In this article, we’ll explore how to use Grunt in a project to speed up and change the way you develop websites. We’ll look briefly at what Grunt can do, before jumping into how to set up and use its various plugins to do all of the heavy lifting in a project.

We’ll then look at how to build a simple input validator, using Sass as a preprocessor, how to use grunt-cssc and CssMin to combine and minify our CSS, how to use HTMLHint to make sure our HTML is written correctly, and how to build our compressed assets on the fly. Lastly, we’ll look at using UglifyJS to reduce the size of our JavaScript and ensure that our website uses as little bandwidth as possible.

Grunt JS
Grunt.js is a JavaScript task runner that helps you perform repetitive tasks such as minification, compilation, unit testing or linting.

Getting Started With Grunt

Most developers would agree that the speed and pace of JavaScript development over the last few years has been pretty astounding. Whether with frameworks such as Backbone.js and Ember.js or with communities such as JS Bin, the development of this language is changing not only the way we experience websites as users but also the way we build them.

Further Reading on SmashingMag:

When you are working with JavaScript, you will likely need to execute multiple tasks regularly. While this is pretty much a given in most projects, it’s a time-consuming and repetitive way to work. Being in such an active community, you would assume that tools are available to automate and speed up this process. This is where Grunt comes in.

What Is Grunt?

Built on top of Node.js, Grunt is a task-based command-line tool that speeds up workflows by reducing the effort required to prepare assets for production. It does this by wrapping up jobs into tasks that are compiled automatically as you go along. Basically, you can use Grunt on most tasks that you consider to be grunt work and would normally have to manually configure and run yourself.

While earlier versions came bundled with plugins like JSHint and Uglyify, the most recent release (version 0.4) relies on plugins for everything.

What kind of tasks? Well, the list is exhaustive. Suffice it to say, Grunt can handle most things you throw at it, from minifying to concatenating JavaScript. It can also be used for a range of tasks unrelated to JavaScript, such as compiling CSS from LESS and Sass. We’ve even used it with blink(1) to notify us when a build fails.

Why Use Grunt?

One of the best things about it is the consistency it brings to teams. If you work collaboratively, you’ll know how frustrating inconsistency in the code can be. Grunt enables teams to work with a unified set of commands, thus ensuring that everyone on the team is writing code to the same standard. After all, nothing is more frustrating than a build that fails because of little inconsistencies in how a team of developers writes code.

Grunt also has an incredibly active community of developers, with new plugins being released regularly. The barrier to entry is relatively low because a vast range of tools and automated tasks are already available to use.

Setting Up

The first thing to do in order to use Grunt is to set up Node.js. (If you know nothing about Node.js, don’t worry — it merely needs to be installed in order for Grunt to be able to run.)

Once Node.js is installed, run this command:


$ npm install -g grunt-cli

To make sure Grunt has been properly installed, you can run the following command:


$ grunt --version

The next step is to create a package.json and a gruntfile.js file in the root directory of your project.

Creating the package.json File

The JSON file enables us to track and install all of our development dependencies. Then, anyone who works on the project will have the most current dependencies, which ultimately helps to keep the development environments in sync.

Create a file in the root of your project that contains the following:


{
    "name" : "SampleGrunt",
    "version" : "0.1.0",
    "author" : "Brandon Random",
    "private" : true,

    "devDependencies" : {
        "grunt" :                   "~0.4.0"
    }
}

Once you have done this, run the following command:


$ npm install

This tells npm which dependencies to install and places them in a node_modules folder.

Creating the gruntfile.js File

Gruntfile.js is essentially made up of a wrapper function that takes grunt as an argument.


module.exports = function(grunt){

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

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

};

You are now set up to run Grunt from the command line at the root of your project. But if you do so at this stage, you will get the following warning:


$ grunt

> Task “default” not found. Use –force to continue.

We’d get this because we haven’t specified any tasks or dependencies yet other than Grunt. So, let’s do that. But first, let’s look at how to extend the package.json file.

Extending the package.json File

The best thing about working with Node.js is that it can find packages and install them in one go, simply based on the contents of the package file. To install all of the new dependencies, just add this to the file:


{
    "name" : "SampleGrunt",
    "version" : "0.1.0",
    "author" : "Mike Cunsolo",
    "private" : true,

    "devDependencies" : {
        "grunt" :                       "~0.4.0",
        "grunt-contrib-cssmin":         "*",
        "grunt-contrib-sass":           "*",
        "grunt-contrib-uglify":         "*",
        "grunt-contrib-watch":          "*",
        "grunt-cssc":                   "*",
        "grunt-htmlhint":               "*",
        "matchdep":                     "*"
    }
}

And to complete the process? You guessed it:


$ npm install

Loading npm Tasks In Grunt

Now that the packages have been installed, they have to be loaded in Grunt before we can do anything with them. We can load all of the tasks automatically with a single line of code, using the matchdep dependency. This is a boon for development because now the dependency list will be included only in the package file.

At the top of gruntfile.js, above grunt.initConfig, paste this:


require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);

Without matchdep, we would have to write grunt.loadNpmTasks(“grunt-task-name”); for each dependency, which would quickly add up as we find and install other plugins.

Because the plugins are loaded into Grunt, we may start specifying options. First off is the HTML file (index.html), which contains the following:


<!DOCTYPE html>
<html lang="en">

    <head>

        <meta charset="utf-8">
        <meta name="viewport"   content="width=device-width; initial-scale=1.0; maximum-scale=1.0;">

        <title>Enter your first name</title>

        <link rel="stylesheet"  href="build/css/master.css">

    </head>

    <body>

        <label for="firstname">Enter your first name</label>
        <input id="firstname" name="firstname" type="text">
        <p id="namevalidation" class="validation"></p>

        <script type="text/javascript" src="build/js/base.min.js"></script>

    </body>

</html>

Validating With HTMLHint

Add this configuration to grunt.initConfig:


htmlhint: {
    build: {
        options: {
            'tag-pair': true,
            'tagname-lowercase': true,
            'attr-lowercase': true,
            'attr-value-double-quotes': true,
            'doctype-first': true,
            'spec-char-escape': true,
            'id-unique': true,
            'head-script-disabled': true,
            'style-disabled': true
        },
        src: ['index.html']
    }
}

A plugin is typically configured like this: the plugin’s name (without the grunt-contrib-/grunt- prefix), then one or more targets of your choosing (which can be used to create custom options for the plugin for different files), an options object, and the files it affects. Now, when we run grunt htmlhint from the terminal, it will check through the source file and make sure that our HTML has no errors! However, manually typing this command several times an hour would get tedious pretty quickly.

Automate Tasks That Run Every Time A File Is Saved

The watch task can run a unique set of tasks according to the file being saved, using targets. Add this configuration to grunt.initConfig:


watch: {
    html: {
        files: ['index.html'],
        tasks: ['htmlhint']
    }
}

Then, run grunt watch in the terminal. Now, try adding a comment to index.html. You’ll notice that when the file is saved, validation is automatic! This is a boon for development because it means that watch will silently validate as you write code, and it will fail if the code hasn’t passed the relevant tests (and it will tell you what the problem is).

Note that grunt watch will keep running until the terminal is closed or until it is stopped (Control + C on a Mac).

Keeping The JavaScript As Lean As Possible

Let’s set up a JavaScript file to validate a user’s name. To keep this as simple as possible, we’ll check only for non-alphabetical characters. We’ll also use the strict mode of JavaScript, which prevents us from writing valid but poor-quality JavaScript. Paste the following into assets/js/base.js:


function Validator()
{
    "use strict";
}

Validator.prototype.checkName = function(name)
{
    "use strict";
    return (/[^a-z]/i.test(name) === false);
};

window.addEventListener('load', function(){
    "use strict";
    document.getElementById('firstname').addEventListener('blur', function(){
        var _this = this;
        var validator = new Validator();
        var validation = document.getElementById('namevalidation');
        if (validator.checkName(_this.value) === true) {
            validation.innerHTML = 'Looks good! :)';
            validation.className = "validation yep";
            _this.className = "yep";
        }
        else {
            validation.innerHTML = 'Looks bad! :(';
            validation.className = "validation nope";
            _this.className = "nope";
        }

    });
});

Let’s use UglifyJS to minify this source file. Add this to grunt.initConfig:


uglify: {
    build: {
        files: {
            'build/js/base.min.js': ['assets/js/base.js']
        }
    }
}

UglifyJS compresses all of the variable and function names in our source file to take up as little space as possible, and then trims out white space and comments — extremely useful for production JavaScript. Again, we have to set up a watch task to build our Uglify’ed JavaScript. Add this to the watch configuration:


watch: {
    js: {
        files: ['assets/js/base.js'],
        tasks: ['uglify']
    }
}

Building CSS From Sass Source Files

Sass is incredibly useful for working with CSS, especially on a team. Less code is usually written in the source file because Sass can generate large CSS code blocks with such things as functions and variables. Walking through Sass itself is a little beyond the scope of this article; so, if you are not comfortable with learning a preprocessor at this stage, you can skip this section. But we will cover a very simple use case, using variables, one mixin and the Sassy CSS (SCSS) syntax, which is very similar to CSS!

Grunt’s Sass plugin requires the Sass gem. You will need to install Ruby on your system (it comes preloaded in OS X). You can check whether Ruby is installed with this terminal command:


ruby -v

Install Sass by running the following:


gem install sass

Depending on your configuration, you might need to run this command via sudo — i.e. sudo gem install sass: — at which point you will be asked for your password. When Sass is installed, create a new directory named assets and, inside that, another named sass. Create a new file named master.scss in this directory, and paste the following in it:


@mixin prefix($property, $value, $prefixes: webkit moz ms o spec) {
    @each $p in $prefixes {
        @if $p == spec {
            #{$property}: $value;
        }
        @else {
            -#{$p}-#{$property}: $value;
        }
    }
}
$input_field:            #999;
$input_focus:           #559ab9;
$validation_passed:     #8aba56;
$validation_failed:     #ba5656;
$bg_colour:             #f4f4f4;
$box_colour:            #fff;
$border_style:          1px solid;
$border_radius:         4px;

html {
    background:         $bg_colour;
}

body {
    width:              720px;
    padding:            40px;
    margin:             80px auto;
    background:         $box_colour;
    box-shadow:         0 1px 3px rgba(0, 0, 0, .1);
    border-radius:      $border_radius;
    font-family:        sans-serif;
}

input[type="text"] {
    @include            prefix(appearance, none, webkit moz);
    @include            prefix(transition, border .3s ease);
    border-radius:      $border_radius;
    border:             $border_style $input_field;
    width:              220px;
}

input[type="text"]:focus {
    border-color:       $input_focus;
    outline:            0;
}

label,
input[type="text"],
.validation {
    line-height:        1;
    font-size:          1em;
    padding:            10px;
    display:            inline;
    margin-right:       20px;
}

input.yep {
    border-color:       $validation_passed;
}

input.nope {
    border-color:       $validation_failed;
}

p.yep {
    color:              $validation_passed;
}

p.nope {
    color:              $validation_failed;
}

You will notice that the SCSS extension looks a lot more like CSS than conventional Sass. This style sheet makes use of two Sass features: mixins and variables. A mixin constructs a block of CSS based on some parameters passed to it, much like a function would, and variables allow common fragments of CSS to be defined once and then reused.

Variables are especially useful for hex colours; we can build a palette that can be changed in one place, which makes tweaking aspects of a design very fast. The mixin is used to prefix rules such as for appearance and transitions, and it reduces bulk in the file itself.

When working with a large style sheet, anything that can be done to reduce the number of lines will make the file easier to read when a team member other than you wants to update a style.

In addition to Sass, grunt-cssc combines CSS rules together, ensuring that the generated CSS has minimal repetition. This can be very useful in medium- to large-scale projects in which a lot of styles are repeated. However, the outputted file is not always the smallest possible. This is where the cssmin task comes in. It not only trims out white space, but transforms colors to their shortest possible values (so, white would become #fff). Add these tasks to gruntfile.js:


cssc: {
    build: {
        options: {
            consolidateViaDeclarations: true,
            consolidateViaSelectors:    true,
            consolidateMediaQueries:    true
        },
        files: {
            'build/css/master.css': 'build/css/master.css'
        }
    }
},

cssmin: {
    build: {
        src: 'build/css/master.css',
        dest: 'build/css/master.css'
    }
},

sass: {
    build: {
        files: {
            'build/css/master.css': 'assets/sass/master.scss'
        }
    }
}

Now that we have something in place to handle style sheets, these tasks should also be run automatically. The build directory is created automatically by Grunt to house all of the production scripts, CSS and (if this were a full website) compressed images. This means that the contents of the assets directory may be heavily commented and may contain more documentation files for development purposes; then, the build directory would strip all of that out, leaving the assets as optimized as possible.

We’re going to define a new set of tasks for working with CSS. Add this line to gruntfile.js, below the default task:


grunt.registerTask('buildcss',  ['sass', 'cssc', 'cssmin']);

Now, when grunt buildcss is run, all of the CSS-related tasks will be executed one after another. This is much tidier than running grunt sass, then grunt cssc, then grunt cssmin. All we have to do now is update the watch configuration so that this gets run automatically.


watch: {
    css: {
        files: ['assets/sass/**/*.scss'],
        tasks: ['buildcss']
    }
}

This path might look a little strange to you. Basically, it recursively checks any directory in our assets/sass directory for .scss files, which allows us to create as many Sass source files as we want, without having to add the paths to gruntfile.js. After adding this, gruntfile.js should look like this:


module.exports = function(grunt){

    "use strict";
   require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);

    grunt.initConfig({

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

        cssc: {
            build: {
                options: {
                    consolidateViaDeclarations: true,
                    consolidateViaSelectors:    true,
                    consolidateMediaQueries:    true
                },
                files: {
                    'build/css/master.css': 'build/css/master.css'
                }
            }
        },

        cssmin: {
            build: {
                src: 'build/css/master.css',
                dest: 'build/css/master.css'
            }
        },



### Validating With HTMLHint

Add this configuration to grunt.initConfig:


htmlhint: {
    build: {
        options: {
            'tag-pair': true,
            'tagname-lowercase': true,
            'attr-lowercase': true,
            'attr-value-double-quotes': true,
            'doctype-first': true,
            'spec-char-escape': true,
            'id-unique': true,
            'head-script-disabled': true,
            'style-disabled': true
        },
        src: ['index.html']
    }
}

A plugin is typically configured like this: the plugin’s name (without the grunt-contrib-/grunt- prefix), then one or more targets of your choosing (which can be used to create custom options for the plugin for different files), an options object, and the files it affects. Now, when we run grunt htmlhint from the terminal, it will check through the source file and make sure that our HTML has no errors! However, manually typing this command several times an hour would get tedious pretty quickly.

Automate Tasks That Run Every Time A File Is Saved

The watch task can run a unique set of tasks according to the file being saved, using targets. Add this configuration to grunt.initConfig:


watch: {
    html: {
        files: ['index.html'],
        tasks: ['htmlhint']
    }
}

Then, run grunt watch in the terminal. Now, try adding a comment to index.html. You’ll notice that when the file is saved, validation is automatic! This is a boon for development because it means that watch will silently validate as you write code, and it will fail if the code hasn’t passed the relevant tests (and it will tell you what the problem is).

Note that grunt watch will keep running until the terminal is closed or until it is stopped (Control + C on a Mac).

Keeping The JavaScript As Lean As Possible

Let’s set up a JavaScript file to validate a user’s name. To keep this as simple as possible, we’ll check only for non-alphabetical characters. We’ll also use the strict mode of JavaScript, which prevents us from writing valid but poor-quality JavaScript. Paste the following into assets/js/base.js:


function Validator()
{
    "use strict";
}

Validator.prototype.checkName = function(name)
{
    "use strict";
    return (/[^a-z]/i.test(name) === false);
};

window.addEventListener('load', function(){
    "use strict";
    document.getElementById('firstname').addEventListener('blur', function(){
        var _this = this;
        var validator = new Validator();
        var validation = document.getElementById('namevalidation');
        if (validator.checkName(_this.value) === true) {
            validation.innerHTML = 'Looks good! :)';
            validation.className = "validation yep";
            _this.className = "yep";
        }
        else {
            validation.innerHTML = 'Looks bad! :(';
            validation.className = "validation nope";
            _this.className = "nope";
        }

    });
});

Let’s use UglifyJS to minify this source file. Add this to grunt.initConfig:


uglify: {
    build: {
        files: {
            'build/js/base.min.js': ['assets/js/base.js']
        }
    }
}

UglifyJS compresses all of the variable and function names in our source file to take up as little space as possible, and then trims out white space and comments — extremely useful for production JavaScript. Again, we have to set up a watch task to build our Uglify’ed JavaScript. Add this to the watch configuration:


watch: {
    js: {
        files: ['assets/js/base.js'],
        tasks: ['uglify']
    }
}

Building CSS From Sass Source Files

Sass is incredibly useful for working with CSS, especially on a team. Less code is usually written in the source file because Sass can generate large CSS code blocks with such things as functions and variables. Walking through Sass itself is a little beyond the scope of this article; so, if you are not comfortable with learning a preprocessor at this stage, you can skip this section. But we will cover a very simple use case, using variables, one mixin and the Sassy CSS (SCSS) syntax, which is very similar to CSS!

Grunt’s Sass plugin requires the Sass gem. You will need to install Ruby on your system (it comes preloaded in OS X). You can check whether Ruby is installed with this terminal command:


ruby -v

Install Sass by running the following:


gem install sass

Depending on your configuration, you might need to run this command via sudo — i.e. sudo gem install sass: — at which point you will be asked for your password. When Sass is installed, create a new directory named assets and, inside that, another named sass. Create a new file named master.scss in this directory, and paste the following in it:


@mixin prefix($property, $value, $prefixes: webkit moz ms o spec) {
    @each $p in $prefixes {
        @if $p == spec {
            #{$property}: $value;
        }
        @else {
            -#{$p}-#{$property}: $value;
        }
    }
}
$input_field:            #999;
$input_focus:           #559ab9;
$validation_passed:     #8aba56;
$validation_failed:     #ba5656;
$bg_colour:             #f4f4f4;
$box_colour:            #fff;
$border_style:          1px solid;
$border_radius:         4px;

html {
    background:         $bg_colour;
}

body {
    width:              720px;
    padding:            40px;
    margin:             80px auto;
    background:         $box_colour;
    box-shadow:         0 1px 3px rgba(0, 0, 0, .1);
    border-radius:      $border_radius;
    font-family:        sans-serif;
}

input[type="text"] {
    @include            prefix(appearance, none, webkit moz);
    @include            prefix(transition, border .3s ease);
    border-radius:      $border_radius;
    border:             $border_style $input_field;
    width:              220px;
}

input[type="text"]:focus {
    border-color:       $input_focus;
    outline:            0;
}

label,
input[type="text"],
.validation {
    line-height:        1;
    font-size:          1em;
    padding:            10px;
    display:            inline;
    margin-right:       20px;
}

input.yep {
    border-color:       $validation_passed;
}

input.nope {
    border-color:       $validation_failed;
}

p.yep {
    color:              $validation_passed;
}

p.nope {
    color:              $validation_failed;
}

You will notice that the SCSS extension looks a lot more like CSS than conventional Sass. This style sheet makes use of two Sass features: mixins and variables. A mixin constructs a block of CSS based on some parameters passed to it, much like a function would, and variables allow common fragments of CSS to be defined once and then reused.

Variables are especially useful for hex colours; we can build a palette that can be changed in one place, which makes tweaking aspects of a design very fast. The mixin is used to prefix rules such as for appearance and transitions, and it reduces bulk in the file itself.

When working with a large style sheet, anything that can be done to reduce the number of lines will make the file easier to read when a team member other than you wants to update a style.

In addition to Sass, grunt-cssc combines CSS rules together, ensuring that the generated CSS has minimal repetition. This can be very useful in medium- to large-scale projects in which a lot of styles are repeated. However, the outputted file is not always the smallest possible. This is where the cssmin task comes in. It not only trims out white space, but transforms colors to their shortest possible values (so, white would become #fff). Add these tasks to gruntfile.js:


cssc: {
    build: {
        options: {
            consolidateViaDeclarations: true,
            consolidateViaSelectors:    true,
            consolidateMediaQueries:    true
        },
        files: {
            'build/css/master.css': 'build/css/master.css'
        }
    }
},

cssmin: {
    build: {
        src: 'build/css/master.css',
        dest: 'build/css/master.css'
    }
},

sass: {
    build: {
        files: {
            'build/css/master.css': 'assets/sass/master.scss'
        }
    }
}

Now that we have something in place to handle style sheets, these tasks should also be run automatically. The build directory is created automatically by Grunt to house all of the production scripts, CSS and (if this were a full website) compressed images. This means that the contents of the assets directory may be heavily commented and may contain more documentation files for development purposes; then, the build directory would strip all of that out, leaving the assets as optimized as possible.

We’re going to define a new set of tasks for working with CSS. Add this line to gruntfile.js, below the default task:


grunt.registerTask('buildcss',  ['sass', 'cssc', 'cssmin']);

Now, when grunt buildcss is run, all of the CSS-related tasks will be executed one after another. This is much tidier than running grunt sass, then grunt cssc, then grunt cssmin. All we have to do now is update the watch configuration so that this gets run automatically.


watch: {
    css: {
        files: ['assets/sass/**/*.scss'],
        tasks: ['buildcss']
    }
}

This path might look a little strange to you. Basically, it recursively checks any directory in our assets/sass directory for .scss files, which allows us to create as many Sass source files as we want, without having to add the paths to gruntfile.js. After adding this, gruntfile.js should look like this:


module.exports = function(grunt){

    "use strict";
   require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks);

    grunt.initConfig({

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

        cssc: {
            build: {
                options: {
                    consolidateViaDeclarations: true,
                    consolidateViaSelectors:    true,
                    consolidateMediaQueries:    true
                },
                files: {
                    'build/css/master.css': 'build/css/master.css'
                }
            }
        },

        cssmin: {
            build: {
                src: 'build/css/master.css',
                dest: 'build/css/master.css'
            }
        },

        sass: {
            build: {
                files: {
                    'build/css/master.css': 'assets/sass/master.scss'
                }
            }
        },

        watch: {
            html: {
                files: ['index.html'],
                tasks: ['htmlhint']
            },
            js: {
                files: ['assets/js/base.js'],
                tasks: ['uglify']
            },
            css: {
                files: ['assets/sass/**/*.scss'],
                tasks: ['buildcss']
            }
        },

        htmlhint: {
            build: {
                options: {
                    'tag-pair': true,
// Force tags to have a closing pair
                    'tagname-lowercase': true,
// Force tags to be lowercase
                    'attr-lowercase': true,
// Force attribute names to be lowercase e.g. <div id="header"> is invalid
                    'attr-value-double-quotes': true,
// Force attributes to have double quotes rather than single
                    'doctype-first': true,
// Force the DOCTYPE declaration to come first in the document
                    'spec-char-escape': true,
// Force special characters to be escaped
                    'id-unique': true,
// Prevent using the same ID multiple times in a document
                    'head-script-disabled': true,
// Prevent script tags being loaded in the  for performance reasons
                    'style-disabled': true
// Prevent style tags. CSS should be loaded through 
                },
                src: ['index.html']
            }
        },

        uglify: {
            build: {
                files: {
                    'build/js/base.min.js': ['assets/js/base.js']
                }
            }
        }

    });

    grunt.registerTask('default',   []);
    grunt.registerTask('buildcss',  ['sass', 'cssc', 'cssmin']);

};

We should now have a static HTML page, along with an assets directory with the Sass and JavaScript source, and a build directory with the optimized CSS and JavaScript inside, along with the package.json and gruntfile.js files.

By now, you should have a pretty solid foundation for exploring Grunt further. As mentioned, an incredibly active community of developers is building front-end plugins. My advice is to head on over to the plugin library and explore the more than 300 plugins.

(al)

More Articles on

Hidden Productivity Secrets With Alfred

by Zeno Rocha

Good developers are always looking for ways to be faster and to automate their workflows. Today, we present a series of workflows in Alfred that will boost your productivity and rock your world.

Hidden Productivity Secrets With Alfred

For those who don’t know, Alfred is an award-winning …

Read more
Read more
Read more