[NodeJS] – How to automating front-end tasks with Grunt ?

What is Grunt ? 

The Grunt is a task automation Node.js module .

Official WebSite :http://gruntjs.com/

How to install in your project ?

$ npm install grunt --save-dev

$ npm install grunt-cli -g

What is the minimum structure of the configuration file?

Name : Gruntfile.js

module.exports = function(grunt) {
   grunt.initConfig({
        /*   tasks here */
  });
};

Example

  • Creating minimum structure of the configuration:
$ npm init   // to create the Node.js project ( package.json file )
$ npm install grunt-cli -g 
$ npm install grunt --save-dev

*You need to install the grunt, grunt-cli and create Gruntfile.js file.

  • In this example we want config to copy  a public directory to dist directory. After we will delete the dist directory. For do it we need install plugins.
$ npm install grunt-contrib-copy --save-dev
$ npm install grunt-contrib-clean --save-dev
  • Defining the execution via grunt.registerTask(). First delete and after copy.

 

File Gruntfile.js :

module.exports = function(grunt) {
   grunt.initConfig({
        copy: {
              public: {
                   cwd: 'public', 
                   src: ['**'], 
                   dest: 'dist', 
                   expand: true
              }
         }, 
         clean: {
              dist: {
                  src: ['dist']
              }
         }
  });

  grunt.registerTask('default', ['dist']);
  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');
};

Merging files to improve download performance  

Each browser has a maximum number of simultaneous requests for the same domain, in fact, much reduced in smartphones. If we reach this limit, the next resource to be downloaded will have to wait until one of the requests over.

Another point is latency, which usually affects quite networks of mobile devices. Latency is the time between you perform a request to the server and it starts to be answered.

We can join files of the same type in a single file to reduce the number of requests made by the browser.

  • Plugins required :

grunt-contrib-concat

This plugin is responsible for concatenating files. It does merge files and transforms them all into a single file.

grunt-contrib-uglify

Used to merge the JavaScript files.

<!-- build:js js/index.min.js -->

{ all javascript imports that do you want merge into index.min.js file}

<!-- endbuild -->

grunt-contrib-cssmin

Used to merge the Css files.

Annotations in the files :

<!-- build:css css/index.min.css -->

{ all css imports that do you want merge into index.min.css file}

<!-- endbuild -->

How to use this plugins ?

# To install

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

# To config in Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');

 

We will use the grunt-usemin to generate the configurations of the above plugins.

grunt-usemin

The task useminPrepare processes each page by searching for special comments involving CSS and JavaScript files. For example:

<!-- build:css css/index.min.css -->
<link rel="stylesheet" href="css/base.css">    
<link rel="stylesheet" href="css/index.css">    
<!-- endbuild -->

<!-- build:js js/index.min.js -->
 src="js/index.js">
<!-- endbuild -->

It is from the extracted information based on these comments that it will generate the settings for the three plugins: grunt-contrib-concat, grunt-contrib-uglify, grunt-contrib-cssmin.

The task usemin is called after the three plugins were executed with useminPrepare settings.

Example :

# Installation :

$ npm install grunt-contrib-concat --save-dev
$ npm install grunt-contrib-uglify --save-dev
$ npm install grunt-contrib-cssmin --save-dev
$ npm install grunt-usemin --save-dev
$ npm install grunt-cli -g

# Gruntfile.js file :

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }
  });


  //defining tasks
  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 'usemin']);

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

  //loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
}

# To run automatization :

$ grunt

Optimizing images to improve performance

Studies presented in http://httparchive.org/ show that more than 60% by weight of one page refers to the images.

  • Plugin : grunt-contrib-imagemin

# To install:

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

# To loading, insert it into Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-imagemin');

# To config, insert it into Gruntfile.js:

imagemin: {
   public: {
     expand: true,
     cwd: 'dist/img',
     src: '**/*.{png,jpg,gif}',
     dest: 'dist/img'
   }
}

The src property indicates that we want all png, jpg and gif which are in any subfolder within dist / img.

The task above created the images used in the project distribution folder, therefore, their task should be recorded after copying the files in the last example:

grunt.registerTask('merge', ['useminPrepare', 'concat', 
                                'uglify', 'cssmin', 'usemin', 'imagemin']);

Example :

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }, 

     imagemin: {
      public: {
        expand: true,
        cwd: 'public/img',
        src: '**/*.{png,jpg,gif}',
        dest: 'dist/img'
      }
    }

  });


  //defining tasks

  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 
                                  'usemin', 'imagemin']);

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

  // loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
  grunt.loadNpmTasks('grunt-contrib-imagemin'); 
}

Optimizing via versions to improve performance

Another point that can reduce these requests is to make the files remain in the browser cache. This plugin version the name of the file, so when the file changes it will be reloaded into the cache. It works by generating a hash of the file name.

  • Plugin : grunt-rev

# To install:

npm install grunt-rev --save-dev

# To loading, insert it into Gruntfile.js:

grunt.loadNpmTasks('grunt-rev');

# To config, insert it into Gruntfile.js:

rev: {
      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },

      merge: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
}

# To indicate which versioning strategy used to generate the hash and how many digits it uses, insert it into Gruntfile.js:

rev: {
      options: {
        encoding: 'utf8',  
        algorithm: 'md5', 
        length: 8 
      },

      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },

      merge: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
}

* The options configuration receives an object with three parameters: the encoding of the file, the algorithm used and the size of the generated prefix. We will use UTF-8, MD5 and size 8 respectively.

# To define the order to run :

grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 
'rev:images','rev:merge', 'usemin', 'imagemin']);

Example

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }, 

     imagemin: {
      public: {
        expand: true,
        cwd: 'dist/img',
        src: '**/*.{png,jpg,gif}',
        dest: 'dist/img'
      }
    }, 

    rev: {
      options: {
        encoding: 'utf8',
        algorithm: 'md5',
        length: 8
      },

      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },
      mergefiles: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
    }   

  });

  //defining tasks

  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 
                                  'rev:images','rev:mergefiles', 
                                  'usemin', 'imagemin']);

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

  // loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
  grunt.loadNpmTasks('grunt-contrib-imagemin'); 
  grunt.loadNpmTasks('grunt-rev'); 
}

Now, if we open the dist / index.html file we will see that the tag’s appeal began to point to the review of the file found on disk:

<link rel="stylesheet" href="css/ebb6096a.index.min.css"/>
...
<img src="img/2babcead.grunt-banner.png">
...
 src="js/3ee7db69.index.min.js">

If you run the script again, the file remained with the same name and only changed if the original file has been modified.

How to work with pre-processors?

  • When is this necessary?

When we use any language that needs to be pre-processed for the browser to interpret the code. In this case we can mention CoffeeScript and Less for example. The code made in CoffeeScript is transformed into JavaScript to be interpreted and codes made in Less will be transformed into css.

Examples : 

# CoffeeScript code :

name = "Camila Macedo"

# Will be transformed in the JavaScript code :

(function() {
  var name;

  name = "Camila Macedo";

}).call(this);

 

# The Less code 

@color : red;

p {
    color: @color;
}  

h1 {
    color: @color;
}

# Will be transformed in the css code :

p {
  color: #ff0000;
}
h1 {
  color: #ff0000;
}

# To work with CoffeeScript 

$ npm install grunt-contrib-coffee --save-dev

# To work with Less :

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

# To loading, insert it into Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-less');

# To config, insert it into Gruntfile.js:

coffee: {
   compile: { 
      expand: true,
      cwd: 'public/coffee', 
      src: ['**/*.coffee'],
      dest: 'public/js',
      ext: '.js'
   }
} ,

less: {
   compile: {
      expand: true,
      cwd: 'public/less',
      src: ['**/*.less'],
      dest: 'public/css',
      ext: '.css' 
   }
}

With this configuration each .coffee file and compiled .less go to the js folder and css respectively. For the build process the file extension is changed add the property ext configuration. This property changes the extension of the file copied to the value set.

# For test the result:

$ grunt coffee less

To automate: grunt-contrib-watch

# To install the plugin :

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

# To loading, insert it into Gruntfile.js:

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

# To config, insert it into Gruntfile.js:

watch: {

   coffee: {
      options: {
           event: ['added', 'changed']
       },
      files: 'public/coffee/**/*.coffee',
      tasks: 'coffee:compile'
   },

   less: {
       options: {
          event: ['added', 'changed']
       },
       files: 'public/less/**/*.less', 
       tasks: 'less:compile'
   }
}

# To run :

$ grunt watch

Important: your console after running this task will be locked. The Task watch will run endlessly, it is necessary for it to monitor the modified files.

Example

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }, 

     imagemin: {
      public: {
        expand: true,
        cwd: 'dist/img',
        src: '**/*.{png,jpg,gif}',
        dest: 'dist/img'
      }
    }, 

    rev: {
      options: {
        encoding: 'utf8',
        algorithm: 'md5',
        length: 8
      },

      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },
      mergefiles: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
    }, 

    coffee: {
      compile: { 
        expand: true,
        cwd: 'public/coffee', 
        src: ['**/*.coffee'],
        dest: 'public/js', 
        ext: '.js'
      }
    },

    less: {
      compile: {
        expand: true,
        cwd: 'public/less',
        src: ['**/*.less'],
        dest: 'public/css', 
        ext: '.css'
      }
    }, 

    watch: {
      coffee: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/coffee/**/*.coffee',
        tasks: 'coffee:compile'
      },

      less: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/less/**/*.less', 
        tasks: 'less:compile'
      }
    }

  });

  //register tasks

  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 
                                   'rev:images','rev:mergefiles', 
                                   'usemin', 'imagemin']);

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

  // loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
  grunt.loadNpmTasks('grunt-contrib-imagemin'); 
  grunt.loadNpmTasks('grunt-rev'); 
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');
}

How to realtime linting ?

Plugin : grunt-contrib-jshint

Plugin responsible for verifying that the code follows the rules of language. This plugin analyzes each time you modify or add files to the project. It also carries out suggestions for improvement to after every change that is made in the code.

# To install:

npm install grunt-contrib-jshint --save-dev

# To loading, insert it into Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-jshint');

# To config, insert it into Gruntfile.js:

jshint: {
   js: {
      src: ['public/js/**/*.js']
    }
}

# To config to run after every change in the project, insert it into Gruntfile.js:

watch: { 
   js: {
      options: {
         event: ['changed']
      },
      files: 'public/js/**/*.js',
      tasks: 'jshint:js'
   }
}

# To run :

$ grunt jshint

Example of Output:

# With errors

Running "jshint:js" (jshint) task

   public/js/index.js
     14 |    }
              ^ Missing semicolon.

>> 1 error in 2 files
Warning: Task "jshint:js" failed. Use --force to continue.

# With success 

Running "jshint:js" (jshint) task
>> 2 files lint free.

Done, without errors.

Example:

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }, 

     imagemin: {
      public: {
        expand: true,
        cwd: 'dist/img',
        src: '**/*.{png,jpg,gif}',
        dest: 'dist/img'
      }
    }, 

    rev: {
      options: {
        encoding: 'utf8',
        algorithm: 'md5',
        length: 8
      },

      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },
      mergefiles: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
    }, 

    coffee: {
      compile: { 
        expand: true,
        cwd: 'public/coffee', 
        src: ['**/*.coffee'],
        dest: 'public/js', 
        ext: '.js'
      }
    },

    less: {
      compile: {
        expand: true,
        cwd: 'public/less',
        src: ['**/*.less'],
        dest: 'public/css', 
        ext: '.css'
      }
    }, 

    watch: {
      coffee: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/coffee/**/*.coffee',
        tasks: 'coffee:compile'
      },

      less: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/less/**/*.less', 
        tasks: 'less:compile'
      },

      js: {
        options: {
          event: ['changed']
        },
        files: 'public/js/**/*.js', 
        tasks: 'jshint:js'
      }
    }, 

    jshint: {
      js: {
        src: ['public/js/**/*.js']
      }
    }

  });

  // tasks

  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 
                                  'cssmin', 'rev:imagens',
                                   'rev:mergefiles', 'usemin', 
                                  'imagemin']);

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

  // loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
  grunt.loadNpmTasks('grunt-contrib-imagemin'); 
  grunt.loadNpmTasks('grunt-rev'); 
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
}

How to do live reloading automated ?

  • Plugin : grunt-browser-sync

This plugin do performing live reloading (automatic page refresh) every time a file in the project has changes. You can define which files do want trigger in this process. It still synchronizes locally made actions with your favorite browser on all devices that point to the same page.

If you click a button, each device that is pointing to the same page it will also suffer the action for example, the same action happening when you realize the scroll of the page.

# To install:

npm install grunt-browser-sync --save-dev

# To loading, insert it into Gruntfile.js:

grunt.loadNpmTasks('grunt-browser-sync');

# To config, insert it into Gruntfile.js:

browserSync: {
      public: {
          bsFiles: {
            src : ['public/**/*']
          }, 
         options: {
            server: {
                baseDir: "public"
            }
         }
      }
}

Note that in options indicated by the server property Basedir its application. Think of it as the root of your project when the URL http: // localhost: 3002 / is accessed.

# To run :

grunt browserSync

Example configuration to load the code page that need to be preprocessed. Example: CoffeeScript and Less .

browserSync: {
      public: {
          bsFiles: {
            src : ['public/**/*']
          }, 
         options: {
            watchTask: true,
            server: {
                baseDir: "public"
            }
         }
      }
}

For this, we need to integrate the browserSync with our task watch adding watchTask property: true in options.

You still need to run the two tasks: First browserSync and then watch. We can accomplish this easily by registering a new task called server.

grunt.registerTask('server', ['browserSync', 'watch']);
  • Now you can run : $ grunt server

Your browser automatically opens already pointing to the local IP of your machine displaying the index.html page.

Final Example with all

module.exports = function(grunt) {

   grunt.initConfig({
      /* Copy the files to the 'dist' */
      copy: {
         public: {
           expand: true,
           cwd: 'public',
           src: '**',
           dest: 'dist'
         }
     },

     clean: {
          dist: {
              src: 'dist'
          }
     },

     useminPrepare: {
       html: 'dist/**/*.html'
     },

     usemin: {
       html: 'dist/**/*.html'
     }, 

     imagemin: {
      public: {
        expand: true,
        cwd: 'dist/img',
        src: '**/*.{png,jpg,gif}',
        dest: 'dist/img'
      }
    }, 

    rev: {
      options: {
        encoding: 'utf8',
        algorithm: 'md5',
        length: 8
      },

      images: {
        src: ['dist/img/**/*.{png,jpg,gif}']
      },
      mergefiles: {
        src: ['dist/js/**/*.min.js', 'dist/css/**/*.min.css']
      }
    }, 

    coffee: {
      compile: { 
        expand: true,
        cwd: 'public/coffee', 
        src: ['**/*.coffee'],
        dest: 'public/js', 
        ext: '.js'
      }
    },

    less: {
      compile: {
        expand: true,
        cwd: 'public/less',
        src: ['**/*.less'],
        dest: 'public/css', 
        ext: '.css'
      }
    }, 

    watch: {
      coffee: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/coffee/**/*.coffee',
        tasks: 'coffee:compile'
      },

      less: {
        options: {
          event: ['added', 'changed']
        },
        files: 'public/less/**/*.less', 
        tasks: 'less:compile'
      },

      js: {
        options: {
          event: ['changed']
        },
        files: 'public/js/**/*.js', 
        tasks: 'jshint:js'
      }
    }, 

    jshint: {
      js: {
        src: ['public/js/**/*.js']
      }
    }, 

    browserSync: {
      public: {
        bsFiles: {
          src : ['public/**/*']
        }, 
        options: {
            watchTask: true,
            server: {
                baseDir: "public"
            }
        }
      }
    }

  });

  //registrando task para minificação

  grunt.registerTask('dist', ['clean', 'copy']);

  grunt.registerTask('merge', ['useminPrepare', 
                                  'concat', 'uglify', 'cssmin', 
                                  'rev:images','rev:mergefiles', 
                                  'usemin', 'imagemin']);

  grunt.registerTask('server', ["browserSync", "watch"]);

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

  // loading tasks
  grunt.loadNpmTasks('grunt-contrib-copy'); 
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-usemin'); 
  grunt.loadNpmTasks('grunt-contrib-imagemin'); 
  grunt.loadNpmTasks('grunt-rev'); 
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-browser-sync');
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s