Where to Discuss?

Preface

Goal: Put the whole stuff together using grunt task runner.

Have you ever got tired of saving, and running render command, and then go back to text editor, and doing this over and over again? This is why we need automation.

pug has built in tools to run this automation, and so does other tools. But when in comes to work with many tools, this won’t be sufficient. Imagine you have three tools, and you have to run, three different tools, in three different terminal. This would be exhausting.

As your project grown, you might need other tools, you can utilize task runner such as gulp or grunt.

Source Examples

You can obtain source examples here:

Automation

Grunt is simply an automation.

Suppose that you want to build a website without framework, nor SSG, nor fancy backend using Pug+Stylus+Coffeescript stack. grunt can watch each component, and compile into vanilla.

  • Generate HTML from Pug.

  • Render CSS from Stylus.

  • Compile Javascript from Coffeescript

Grunt: Stylus + Coffeescript + Pug

In short, grunt can watch the whole stack at once.

Downside

Grunt is an old tool.

The task runner is belong to the past for conservative web development. Use bundler for modern web development instead, such as webpack, roller or parcel. Both task runner and bundler might takes some configuration effort.

If you desire vanilla, grunt is still your choice.


Grunt

Tips: Do not get initimidated by long config, once you understans, you can reuse in other project.

Here I represent an automation example on how to build Gruntfile.js.

Official Documentation

Install

$ npm install -g grunt

Since in this project, we are dealing with Pug+Stylus+Coffeescript, we need to install each contrib. We also need to the contrib-watch.

You can also install locally per project, instead of global install.

$ npm install grunt --save-dev
$ npm install grunt-contrib-pug --save-dev
$ npm install grunt-contrib-stylus --save-dev
$ npm install grunt-contrib-coffee --save-dev
$ npm install grunt-contrib-watch --save-dev

Or in package.json

{
  ...
  "devDependencies": {
    "grunt": "^1.0.1",
    "grunt-contrib-pug": "^2.0.0",
    "grunt-contrib-stylus": "^1.2.0",
    "grunt-contrib-coffee": "^2.1.0",
    "grunt-contrib-watch": "^1.1.0"
  }
}

And do $ npm install as a starter before you begin your project.

Gruntfile.js

You also need to a configuration file named Gruntfile.js.

$ touch Gruntfile.js

As an overview, the grunt configuration skeleton, for our little project, is shown as below:

module.exports = function(grunt) {
  // configure the tasks
  let config = {

    // Pug
    pug: {
      ...
    },

    // Stylus
    stylus: {
      ...
    },

    // Coffeescript
    coffee: {
      ...
    },

    //  Watch Files
    watch: {
      pug: {
        ...
      },
      stylus: {
        ...
      },
      coffee: {
        ...
      }
    }

  };

  grunt.initConfig(config);

  // load the tasks
  ...

  // define the tasks
  grunt.registerTask('default', [
    'pug', 'stylus', 'coffee', 'watch'
  ] );

};

Open your favorite text editor and start writing configuration.


Pug Case

Consider our last alert.pug. We are going to compile it using grunt.

One Pug to One HTML

Have a look at using this configuration below:

module.exports = function(grunt) {
  // configure the tasks
  let config = {

    // Pug
    pug: {
      compile: {
        options: {
          pretty: true
        },
        files: {
          'html/alert.html': 'pug/alert.pug' // 1:1 compile
        }
      }
    }
  };

  grunt.initConfig(config);

  // load the tasks
  grunt.loadNpmTasks('grunt-contrib-pug');

  // define the tasks
  grunt.registerTask('default', [
    'pug'
  ] );
};

With this configuration, we can run:

$ grunt pug

Or simply the use the default task

$ grunt

Grunt: One to One Pug Render

This will compile pug/alert.pug to pug/alert.html.

Filters

How about pug/alert-include-source.pug to pug/alert-include-source.html? Allright, this should be as simple as changing line below:

  files: {
    'html/alert-include-source.html': 'pug/alert-include-source.pug'
  }

Let’s see what’s happened!

Grunt: Pug Filter Error

To solve the situation we just need to to add both filters:

  • to render stylus and,
  • to compile coffescript.
  options: {
    pretty: true,
    filters: {
      'stylus': function(block) {
       return require('stylus').render(block);
      },
     'coffee-script': function(block) {
        return require('coffeescript').compile(block);
      },
    },
  },
  files: {
   'html/alertinclude-source.html': 'pug/alert-include-source.pug'
  }

Now the grunt should running fine.

Compile Many Pug at Once

What good is automation if we cannot run many task at once? Doing this is simply as changing files configuration as below:

  files: [ {
    cwd: "pug",
    src: "*.pug",
    dest: "html",
    expand: true,
    ext: ".html"
  } ]

This will compile pug/*.pug into its representative in html/*.html.

Grunt: Compile The Whole Pug Files

As you can see in figure above, now the grunt compile three files.

Watch

This the most automation task from grunt. The watch configuration is simple:

    //  Watch Files
    watch: {
      pug: {
        files: ['pug/**/*'],
        tasks: ['pug'],
        options: {
          interrupt: false,
          spawn: false
        }
      }

To complete the code, is to simply alter the configuration as below:

module.exports = function(grunt) {
  // configure the tasks
  let config = {

    // Pug
    pug: {
      ...
    },
    
    //  Watch Files
    watch: {
      ...
    }
  };

  grunt.initConfig(config);

  // load the tasks
  grunt.loadNpmTasks('grunt-contrib-pug');
  grunt.loadNpmTasks('grunt-contrib-watch');  

  // define the tasks
  grunt.registerTask('default', [
    'pug', 'watch'
  ] );
};

Now the default task will run pug task, then run watch task.

Grunt: Watch Pug

Alter one, or two pug files, to see the watch task result.


Complete Case

As I said earlier, we use this stack Pug+Stylus+Coffeescript. So these stack should be completely covered by Gruntfile.js configuration.

Stylus

The stylus part is simply one file style.styl, so we just need this:

    // Stylus
    stylus: {
      compile: {
        options: {
          compress: false
        },
        files: {
          'css/style.css': 'css/style.styl' // 1:1 compile
        }
      }
    },

Do not forget to watch

    //  Watch Files
    watch: {
      stylus: {
        files: ['css/style.styl'],
        tasks: ['stylus']
      }
    }

And load task.

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

Coffeescript

The coffeescript part is also simply one file script.coffee.

    // Coffeescript
    coffee: {
      compile: {
        files: {
          'js/script.js': 'js/script.coffee' // 1:1 compile
        }
      }
    },

Also, do not forget to watch

    //  Watch Files
    watch: {
      coffee: {
        files: ['js/script.coffee'],
        tasks: ['coffee']
      }
    }

And also load task.

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

Complete Configuration

Finally we need to register all tasks.

  // define the tasks
  grunt.registerTask('default', [
    'pug', 'stylus', 'coffee', 'watch'
  ] );

Grunt: Watch All: Pug + Styles + Coffeescript

Summary

As a conclusion, the complete configuration code is shown as below:

module.exports = function(grunt) {
  // configure the tasks
  let config = {

    // Pug
    pug: {
      compile: {
        options: {
          data: {
            debug: false
          },
          pretty: true,
          filters: {
            'stylus': function(block) {
              return require('stylus').render(block);
            },
            'coffee-script': function(block) {
              return require('coffeescript').compile(block);
            },
          },
        },
        files: [ {
          cwd: "pug",
          src: "*.pug",
          dest: "html",
          expand: true,
          ext: ".html"
        } ]
      }
    },

    // Stylus
    stylus: {
      compile: {
        options: {
          compress: false
        },
        files: {
          'css/style.css': 'css/style.styl' // 1:1 compile
        }
      }
    },

    // Coffeescript
    coffee: {
      compile: {
        files: {
          'js/script.js': 'js/script.coffee' // 1:1 compile
        }
      }
    },

    //  Watch Files
    watch: {
      pug: {
        files: ['pug/**/*'],
        tasks: ['pug'],
        options: {
          interrupt: false,
          spawn: false
        }
      },
      stylus: {
        files: ['css/style.styl'],
        tasks: ['stylus']
      },
      coffee: {
        files: ['js/script.coffee'],
        tasks: ['coffee']
      }
    }

  };

  grunt.initConfig(config);

  // load the tasks
  grunt.loadNpmTasks('grunt-contrib-pug');
  grunt.loadNpmTasks('grunt-contrib-stylus');
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // define the tasks
  grunt.registerTask('default', [
    'pug', 'stylus', 'coffee', 'watch'
  ] );

};

Grunt: Vim Gruntfile.js


What’s Next?

Consider continue reading [ Bundler - Webpack - Part One ].