Introduction to Grunt

Not long ago, the good practices comply with JavaScript code was pretty tedious. How to compile everything into a single file? How to turn Sass into CSS files? How…?

They began to be some tools like Codekit , they did all this work for you, and for some time it was fine.

However, with the arrival of Node.js and its adoption by the community they began to emerge tools that avoided the tedious repetition of executing tasks and here is why came Grunt. Please do not confuse with Groot .

What is Grunt?

As I advance, Grunt is a tool built on Node.js. It is a command line tool that allows you to run tasks from it. Think of an assistant in charge of doing all the dirty work for you.

Grunt community is very vibrant and there are jobs for all tastes and colors from compile LESS CSS from Sass and run tests. For example I have a set of tasks for the blog that allows me, when you change, compile the CSS and JavaScript, versioning all files and upload them to the server.

Why not Grunt?

The answer is highly personal and largely based on my experience. There is great community Gulp or Broccoli for example.

What attracts me is its consistency Grunt and simple to understand that it is for the whole team.

Gulp is great if you know JavaScript, you know how they work in Node Streams and want enfangarte writing some code. However, since I work with people who do not need to know JavaScript but may need to do some temporary or permanent adjustment tasks, Grunt seems easier for the whole family .

Starting with Grunt

The first thing to do is to use Grunt install Node.js if you have not already installed.
Once installed Node, we have to install Grunt:

$ Npm install -g grunt-cli

This step only have to do once in your life (for each computer, of course). The next step is to create a file package.json and other file gruntfile.js in the root directory of your project.

The file package.json

Once you have created the file, put something in it:

{ “name” : “MyGrunt” , “version” : “1.0.0” , “author” : “Reader” , “private” : true , “devDependencies” : { “grunt” : “0.4.0 ~” } }

Most notable is devDependencies where indicated grunt . Basically we are saying to Node that in order to develop our project we need Grunt. Node can now install the unit:

$ Npm install

Once installation is complete you should have a folder node_modules in the directory of your project, where a copy of grunt is stored. Fantastic!

Creating the file Gruntfile.js

The Gruntfile.js file must also go to the root folder of your project. Let’s put this in the file:

module . exports =  function ( grunt ) {   
  grunt . registerTask ( 'default' ,  function ( ) { 
    grunt . log . writeln ( 'Hello World!' ) ; 
  } ) ; 
} ;

Now run the following from the root of your project in the terminal:

$ Grunt

If all goes well, you should see on your terminal, Hello world! .

But how does it work? Well, I will not explain each of the functions Grunt since it is a bit outside the scope of the article and I still believe that if you come to read this far, you deserve an explanation, right?

Basically Grunt load this file and invokes the function spent in module.exports passing grunt as the only parameter.

Since we want grunt do things for us, we use registerTask to register a new task, called default . default is a special task that runs when you call grunt without any additional parameters. We could have used registerTask (‘hello’ but instead of just grunt , would have to write grunt hello to get the same result.

In this case, the task contains a function call log.writeln that it does is write a line to the console, which is what we see. All right!

Well here everything Grunt.

But… This is not at all what I promised me.

You are right. I will add little more.

Grunt and configuration parameters

Grunt allows us to pass parameters too, passing the task that we are running. I do not know anyone named world so let’s fix that task:

module . exports =  function ( grunt ) {   
  grunt . registerTask ( 'default' ,  function ( name ) { 
    grunt . log . writeln ( 'Hello'  + name +  '!' ) ; 
  } ) ; 
} ;

So far nothing new, we’ve just added the parameter name and add it to function as such. How do we pass the parameter now Grunt?

$ Grunt default: ‘Function’

And we will see the greeting we expected:

Running “default: Function” (default) task Function Hello!
All right! The pity is that we now need to pass the name of the task because if we put one parameter, Grunt think the task name is Function 13 .

Our humble task works well so far but with this modification we need to pass a parameter or display:

Undefined Hello!
And if we keep a parameter somewhere to use it by default? Let’s see how we can achieve this:

module . exports =  function ( grunt ) {   
  grunt . initconfig ( { 
    values ​​:  { 
      greeting :  'World' 
    } 
  } ) ;

  grunt . registerTask ( 'default' ,  function ( nombre ) { 
    nombre = nombre || grunt . config . get ( 'valores' ) . saludo ; 
    grunt . log . writeln ( 'Hello '  + name +  ''! ) ; 
  } ) ; 
} ;

The function initconfig values ​​allows us to grunt to remember them. In this case, we use this value if we are not to greet a name. undefined after all is nothing, right? We salute properly!

If we run this now with no parameters or obtain satisfactory results. However, there is another use for settings and is using it in the templates. Let’s see:

module . exports =  function ( grunt ) {   
  grunt . initconfig ( { 
    values ​​:  { 
      greeting :  'World' 
    } 
  } ) ;

  grunt . registerTask ( 'HelloWorld' ,  function ( ) { 
    grunt . log . writeln ( 
      grunt . template . process ( 'Hello <% = valores.saludo%>' ) 
    ) ; 
  } ) ; 
} ;

We have now created a new task called hello-world that if we run back to see the famous message. But what is happening here?

Thanks to the function template.process , we can change a string values ​​stored in the configuration of Grunt. You see it’s really useful. In this case it needs to use template.process but, as we shall see, there are places that do not need to use this function and this utility will be even more versatile.

Before moving on to another topic, I would like to show a new feature that can combine everything we have learned. Since we have seen that we can assign values ​​to save them as settings, see how we can save your settings elsewhere, for example in a file. We will create a file called config.json you’ll create in the same directory. The content is nothing new:

{ 
  "values"  :  { 
    "greeting"  :  "World" 
  } 
}

All we have done is to change to valid JSON. Now let’s see how we can make Grunt use this file:

module . exports =  function ( grunt ) {   
  grunt . initconfig ( grunt . file . readJSON ( 'config.json' ) ) ;

  grunt . registerTask ( 'hello-world' ,  function ( ) { 
    grunt . log . writeln ( 
      grunt . template . process ( 'Hello <% = valores.saludo%>' ) 
    ) ; 
  } ) ; 
} ;

The function file.readJSON receives a file path and returns a JavaScript object. In fact, if we execute the task again, the same message reappears. Fantastic!

A real use case

Now we will use Grunt as you normally will use. However these functions we have seen, are really useful in some cases.

Let us in situation. We have a fantastic site with two JavaScript files: funciones.js and app.js . From what we’ve read somewhere, it would be nice to join them both into one and compress code.

The structure we have is this:

├── Gruntfile.js
├── config.json
├── node_modules
│ └── grunt
└── web
    ├── index.html
    Js └──
        ├── app.js
        └── funciones.js

Grunt separate website and company as a good practice, as if she was all mixed up and would be more cumbersome and less clear, is not it?

As I said at the beginning of the article, the ecosystem of tasks for Grunt is quite high so let’s see the tasks we need:

So we will install tasks:

$ Npm install contrib-concat grunt-grunt-contrib-uglify –save-dev

This allows us to install both tasks with a single command file and also updates our package.json .

Now we have to charge for Grunt tasks known to exist, to give you an idea, it is similar to the idea of registerTask except that, instead of carrying code that we write, use the task that we have installed.

This is how would our Gruntfile.js :

module . exports =  function ( grunt )  {   
  grunt . loadNpmTasks ( 'grunt-contrib-concat' ) ; 
  grunt . loadNpmTasks ( 'grunt-contrib-uglify' ) ; 
} ;

With loadNpmTasks we are telling Grunt, hey, there are two tasks calledgrunt-contrib-concat and grunt-contrib-uglify I installed withNPM , do I load? . And he will obediently.

Configuration

It’s a good idea to add a setting to indicate where things are. Now it will seem somewhat silly as we only have three directories but, after all, this is just an example, right? This way you can use the same settings in new projects that share similar structures or even different, with only update the configuration.

Let’s do it. Add the following just above tasks:

grunt . initconfig ( {   
  routes :  { 
    root :  'web' , 
    js :  '<% = rutas.raiz%> / js' 
  } 
} ) ;

Here we create the object paths pointing to the two folders you have. You see, we use the templates that explained above. In this case no need to use any particular function for Grunt understand what you’re talking about.

Now that we have everything ready, we will create the settings on each task.

Before compressing, files need to be together. So we configure the task concat :

module . exports =  function ( grunt )  {   
  grunt . initconfig ( { 
    routes :  { 
      root :  'web' , 
      js :  '<% = rutas.raiz%> / js' 
    } , 
    concat :  { 
      app :  { 
        src :  [ 
          '< rutas.js% =%> / funciones.js' ,  
          '<% = rutas.js%> / app.js' 
        ] , 
        dest :  '<% = rutas.js%> / app.min.js' 
      } 
    } 
  } ) ;

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

Go, we still use loadInitConfig ! All we have to do is create an object with the name of the task ( concat ) create a goal ( app ) and define the sources (first function and then app ) and where we want to stop the file,app.min.js . Note that the name of the target could have been anyone, have chosen app to be a sensible name but could have chosen candy and would have been worth. The interesting thing is that we could define two objectives:

concat :  {   
  app :  { 
    src :  [ 
      '<% = rutas.js%> / funciones.js' , 
      '<% = rutas.js%> / app.js' 
    ] , 
    dest :  '<% = rutas.js% > /app.min.js' 
  } , 
  failure :  { 
    src :  [ 
      '<% = rutas.js%> / app.js' , 
      '<% = rutas.js%> / funciones.js' 
    ] , 
    dest :  ' <% = rutas.js%> / fracaso.min.js' 
  } 
}

In the other example we define two different objectives, we already know and failure and we placed app before functions and that can not be!To run a specific objective should use the following syntax:

$ Grunt [task-name]: [target name]

For example: $ grunt concat: failure and executed only failure .If we ran $ grunt concat be implemented all the goals one after another.
Uglify allows us to do the task but also concatenate concat continues to be an interesting task and wanted to demonstrate the use of various tasks and aliases.

After this little diversion, we configure uglify :

uglify :  {   
  app :  { 
    src :  '<% = concat.app.dest%>' , 
    dest :  '<% = concat.app.dest%>' 
  } 
}

Here’s a good trick. Configure concat and now use your file, we remember would be located in web / js / app.min.js . Since everything is the same configuration, we can use the template system to resolve the file path.

Now we have one last step. Let’s create a task that encompasses the two we just created:
grunt . registerTask ( ‘scripts’ , [ ‘concat: app’ , ‘uglify: app’ ] ) ;

We are already familiar with registerTask , the good thing is that this function allows us to pass an array of tasks, with or without purpose, to run.Thus, executing $ grunt scripts will run everything just did. In this way just to create an alias that includes a number of tasks.

It is advisable to create aliases separated by subject, thus it is easy to call the tasks in a specific order and it is easy to reuse them in the future. There are tasks that use so other tasks, tidy know!

Finally, we might add scripts to aliases special default to just run $ grunt , our task is complete. This is how is our Gruntfile.js full.

module . exports =  function ( grunt )  {   
  grunt . initconfig ( { 
    routes :  { 
      root :  'web' , 
      js :  '<% = rutas.raiz%> / js' 
    } , 
    concat :  { 
      app :  { 
        src :  [ 
          '< rutas.js% =%> / funciones.js' , 
          '<% = rutas.js%> / app.js' 
        ] , 
        dest :  '<% = rutas.js%> / app.min.js' 
      } 
    } , 
    uglify :  { 
      app :  { 
        src :  '<% = concat.app.dest%>' , 
        dest :  '<% = concat.app.dest%>' 
      } 
    } 
  } ) ;

  grunt . registerTask ( 'scripts' ,  [ 'concat: app' ,  'uglify: app' ] ) ; 
  grunt . registerTask ( 'default' ,  [ 'scripts' ] ) ;

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

By now you should have a solid foundation to begin working with Grunt yourself. As I said, Grunt has a very active community of developers creating tasks for different needs.

In the future I will write some more advanced techniques you can use with Grunt.
Feel free to comment below your opinions about Grunt and what utilities you have driven from it.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top