Advanced Basics: Using Task Runner in Visual Studio

January 15th, 2021

If the world is running on JavaScript, why not automate your client-side tasks through Visual Studio's Task Runner?

With JavaScript as currently the primary language for developers, it made sense when Microsoft released Visual Studio 2019 (or more recently Visual Studio 2022) to include client-side tools to simplify the JavaScript development process.

One of those tools was a Task Runner. 

When the Task Runner was introduced, developers of the C# community never even knew what it was or how it worked (including me).

Now, since I've dug into it, it proves it's another great tool in the DevOps toolbelt.

What is a Task Runner

A Task Runner is a tool to automate repetitive client-side tasks.

The tool runs off of a configuration file (either Gulp or Grunt) to execute on any number of static assets in a directory.

For this post, we'll focus on using Gulp with a gulpfile.js.

Setting up the Task Runner

In Visual Studio 2019 (or 2022), confirm you are running the latest Node version.

  1. Download the latest version of Node and install it normally.
  2. In Visual Studio, go to Tools, Options.
  3. Expand the Projects and Solutions, Web Package Management, then External Web Tools.
  4. Confirm $(PATH) is at the top of your "location of external tools"

If you installed NodeJs properly, it will be in your system path. The $(PATH) will point to that when a build process occurs.

Next, since a Task Runner uses Node for it's engine, we need a populated package.json file to hold the installed packages used in our Task Runner.

For starters, let's install our Gulp package.

  1. Open the Package Manager Console (View / Windows / Package Manager Console)
  2. Change into the directory containing the root of your solution.
  3. Type: npm install -g gulp-cli
  4. Then type: npm install --save-dev gulp

Examine the package.json file in the directory. It should contain a gulp entry:

"gulp": "^4.0.2"

Once we have our package installed, we can use it in our test.

Testing Gulp

One of the best ways to test it out is to create a simple task. Think of it like a "Hello World" for our Task Runner.

  1. Create a gulpfile.js in the root of your project with the following contents.

    var gulp = require('gulp');

    gulp
    .task('testTask', function (done) {
        console.log('Hello World! We finished a task!');
        done();
    });
  2. Save the file.
  3. Open your Task Runner Explorer.
  4. Right-click on your testTask and run it. 

You should see it executed to the right in the output window.

Troubleshooting

One of the issues I kept running into with the Task Runner was it kept saying "No Task Runner Configurations were found."

If you see this and you have a valid configuration file, click on the refresh button on the left and it should reappear.

The trick is if you make any changes to the gulpfile.js, it will unload from the Task Runner.

Click the refresh button to gain it back. NOTE: This has been fixed in Visual Studio 2022.

If you don't have a valid configuration file, check your Output Window (View / Output Window) and set your "Show Output From:" to Task Runner Output.

This will tell you where the error resides.  

Structure of a gulpfile.js

While every gulpfile is considered a "snowflake," the one structured pattern I see the most contains the following:

The packages at the top are the modules installed through your "npm installs" 

For every automated concept in a gulpfile.js, there should be two tasks: a processing task and a cleanup task.

The processing task is meant to achieve what we want automated.

The cleanup task is meant to delete the created or processed files.

All of the examples below will contain this type of structure so you can reuse these tasks in your own projects.

Creating Tasks

For most tasks, you don't want to get confused by the various paths for each files which is why I create a source and destination object for my assets.

Now, the most used tasks for Task Runners are bundling and minifying. These two tasks are what makes websites fast.

I've always said speed is what keeps your audience on your site. If you create code small enough and relevant to a particular page, shove them into small chunks, and shrink down to their minimal size, you'll create websites that outperform 95% of your competition.

With that said, we'll focus on JavaScript and CSS.

Bundling/Minifying CSS

Whether it's SASS or LESS, bundling CSS is a necessity when building fast websites.

If you have a solid way of organizing your CSS, using Task Runners to bundle up stylesheets is an easy task. In our example, we'll be using SASS.

var path = require('path'),
    gulp = require('gulp'),
    rename = require('gulp-rename'),
    gp_clean = require('gulp-clean'),
    gp_sass = require('gulp-sass');

var
 basePath = path.resolve(__dirname, "wwwroot");

var
 srcPaths = {
   sass: [
        path.resolve(basePath, 'scss/blog-page.scss'),
        path.resolve(basePath, 'scss/users-page.scss'),
        path.resolve(basePath, 'scss/login-page.scss')
    ]
};

var
 destPaths = {
    css: path.resolve(basePath, 'css')
};

/* SASS/CSS */

gulp.task('sass_clean', function () {
    return gulp.src(destPaths.css + "*.*", { read: false })
        .pipe(gp_clean({ force: true }));
});

gulp
.task('sass', function() {
    return gulp.src(srcPaths.sass)
        .pipe(gp_sass({outputStyle: 'compressed'}))
        .pipe(rename({
            suffix: '.min'
        }))
        .pipe(gulp.dest(destPaths.css));
});

/* Defaults */

gulp.task('cleanup', gulp.series(['sass_clean']));

gulp
.task('default', gulp.series(['sass']));

You want your tasks to be as quick as possible.

Notice at the bottom. I have a global 'default' and 'cleanup' where you can have an array of tasks to cleanup and process your static files.

The gp_sass package eliminates two tasks with one method. It converts our stylesheets into plain CSS and compresses ("bundles") them into one. The last step adds a suffix of '.min' to the end of the file.

Minifying JavaScript

Ahh, the 400-pound gorilla in the room.

With JavaScript, it's only a matter of selecting the right libraries to perform the bundling and minifying of the project's scripts.

Again, if you have a method of organizing your scripts into a concise folder structure, the process will flow easier.

var path = require('path'),
    gulp = require('gulp'),
    gp_clean = require('gulp-clean'),
    gp_minify = require("gulp-minify");

var
 basePath = path.resolve(__dirname, "wwwroot");
var
 srcPaths = {     js: [         path.resolve(basePath, 'src/common/validation.js'),         path.resolve(basePath, 'src/common/treeTable.js')     ] };
var
 destPaths = {     js: path.resolve(basePath, 'js') };
/* JavaScript */
gulp.task('js', function () {     return gulp.src(srcPaths.js)         .pipe(gp_minify({ noSource: true }))         .on('error',             function (err) {                 console.error('Error!', err.message);             })         .pipe(gulp.dest(destPaths.js)); });
gulp
.task('js_clean', function () {     return gulp.src(path.resolve(destPaths.js, '**/*.js'), { read: false })         .pipe(gp_clean({ force: true })); });
/* Defaults */
gulp.task('cleanup', gulp.series(['js_clean']));
gulp
.task('default', gulp.series(['js']));

This gives you a simple way of minifying your JavaScript and placing it into a run-time folder.

Conclusion

In today's post, we took a simple tool and automated tasks to make our life a little easier.

While bundling and minifying are the basics when using a Task Runner, there are a ton of other tasks available including minifying HTML or optimizing your images to make them smaller and load faster.

Always be mindful when automating your tasks to find the right balance between fast tasks and useful client-side tasks.

If Visual Studio Code is your editor of choice, refer to this post on Microsoft's site.

Do you have a favorite Gulp or Grunt task? How many tasks run through your Task Runner? Post your comments below and let's discuss. 

Other Visual Studio Task Runner Posts