Though Visual Studio started as an IDE for building applications based on Microsoft Technologies, lately the IDE is expanding its domain by adding capabilities to build apps based on other technologies too, including many open source technologies. Projects like Node.js Tools for Visual Studio (NTVS) and Python Tools for Visual Studio (PTVS) prove the openness the IDE is adopting these days.
Front-end web development went through a number of changes in the recent past. One primary change especially when it comes to developing on the Microsoft stack has been to take advantage of the rich ecosystem of tools created by the community. At times, it feels like front-end web development was always incomplete without tools like Bower, Grunt and Gulp - some JavaScript based task runners and package manager. It would be hard to find an open source front-end project on GitHub that doesn’t use these tools. So any rich web development environment not taking advantage of this already existing rich ecosystem, may sound a bit dated. Being a fan of Visual Studio for years, I was expecting these features to be supported by the IDE. Thankfully, the IDE got a couple of extensions recently that fills this gap for us.
This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free .NET tutorials from experts
In this article, we will see how these tools can be used to build modern web apps on Visual Studio. This articles uses Visual Studio 2013 and the latest Preview/CTP5 version of Visual Studio 2015.
Installing the Visual Studio Extensions
If you are a user of Visual Studio for a long time, you would be using Web Essentials too. Web Essentials is an extension from Microsoft created by Mads Kristensen, that adds a lot of features to make web development better on Visual Studio. In Visual Studio 2015, Web Essentials adds the features of adding Grunt and Gulp to Visual Studio. For VS 2013, we have a plugin called Grunt Launcher, written by a community member to add tooling support for Grunt and Gulp.
Before you move forward, make sure that you have these extensions installed. They can be installed either directly from Visual Studio menu (Tools > Extensions and Updates), or by downloading the vsix files from Visual Studio Extensions Gallery.
- For VS 2013: Grunt Launcher
- For VS 2015: Web Essentials
You need to restart Visual Studio in order to get these extensions working.
An Introduction to Bower, Grunt and Gulp
The tools that I have been mentioning till now, namely Bower, Grunt and Gulp, work on top of Node.js. You should have Node.js already installed on your system. The three tools we are going to work on can be installed using NPM (Node Package Manager). Following are the commands and a brief note on these tools:
bower: Bower is a package manager used to manage front-end packages in a project. It can be used to fetch packages from any Git repository. By default, it uses GitHub as the source of repositories. Bower has to be installed globally using the following command:
npm install bower -g
grunt: Grunt is a task runner. It can be used to perform a series of operations like concatenation, minification, copy, clear, check for quality using JS Hint, start an express server and similar tasks. Like bower, grunt also needs to be installed globally. The list of tasks have to be configured on a special file named Gruntfile.js. This name is a convention and shouldn’t be changed. The configuration has to be defined using an object literal. Grunt has to be installed globally using the following command:
npm install grunt –g
gulp: Gulp is an alternative to Grunt. Unlike Grunt, tasks for gulp have to be defined using pipes and streams. The tasks have to be defined in a file named Gulpfile.js; like Grunt, this file has to be named by convention. Gulp has to be installed globally as well using the following command:
npm install gulp -g
Note: To use Grunt and Gulp in VS 2015 Preview/CTP and in VS 2013, you need to have them installed globally. Final release of VS 2015 will use the packages installed in the project and won’t need the global packages.
Now that you got some idea on the tools to be used, let’s use them in an application.
About the Demo Application
We will not be building the application from scratch. Instead, I will add grunt and gulp support to an existing application. The demo application is a Movie List application (If you read my article on Node.js tools on Visual Studio, I am using the same demo here). It is a simple movie list application that has the following features:
- Shows a list of movies
- Adding a new movie
- Marking a movie as released
- Marking a movie as watched
If you want to follow along this article, download the sample code of this article and open the MovieList – 2013 or, MovieList – 2015 on either VS 2013 or VS 2015 respectively. These folders contain all of the required code, but don’t have NPM packages, Bower packages and Grunt or Gulp added to them. VS 2013 project is created from an empty ASP.NET template and the VS 2015 project is created using ASP.NET MVC 5 starter template. The reason for choosing two different templates is to show that Grunt and Gulp can be used with any kind of application.
Both these projects contain Entity Framework code first classes and ASP.NET Web API or MVC 6 controllers to serve data. On the front-end, they have just one view built using Bootstrap and Angular. This view fetches data from the API and binds it to the view. You won’t be able to run the application unless you have the required scripts and styles at their target places.
In rest of the article, we will add the following Grunt or Gulp tasks to this application:
- Install and Copy bower components
- Run Karma tests on Chrome
- Concatenate JavaScript files
- Copy concatenated JavaScript file and CSS file to a target folder
- Clear contents of a folder
Using Grunt and Bower in Visual Studio
Once you open the project inside Visual Studio, check the files and folders to get familiar with the code and the way it is organized. The project was created using ASP.NET Empty project template and then I added some other files to it. As you see, the project doesn’t have any existing Node and bower dependencies. Let’s add them now.
Add a new file to the project and name it package.json. This file will contain the list of Node.js dependencies required by the project. Add the following content to the file:
{
"name": "Movie-list",
"preferGlobal": true,
"version": "0.1.0",
"author": "Ravi Kiran",
"description": "A sample movie list application",
"bin": {
"package-name": "./bin/package-name"
},
"dependencies": {
},
"analyze": false,
"devDependencies": {
"grun": "^0.4.5",
"grunt-bowercopy": "^1.2.0",
"karma": "^0.12.31",
"karma-jasmine": "^0.3.5",
"grunt-karma": "^0.10.1",
"grunt-contrib-copy": "^0.7.0",
"grunt-contrib-concat": "^0.5.0",
"grunt-contrib-clean": "^0.6.0"
},
"license": "MIT",
"engines": {
"node": ">=0.6"
}
}
If you type-in the dependencies, the IDE shows a list of suggestions based on the text entered. It fetches this information from the global NPM registry. It also shows suggestions for versions. Figure-1 shows a screenshot of the intellisense:
Figure 1: npm intellisense
To install these packages, you can either run the following command in a command prompt:
npm install
Or, you can use menu options in Visual Studio. The options are different in VS 2013 and VS 2015. In VS 2013, this option is available on right clicking the package.json file, as shown in Figure-2:
Figure 2: NPM packages in Visual Studio 2013
In VS 2015, this option is available through context menu on NPM node under Dependencies node. Figure -3 shows it:
Figure 3: NPM packages in Visual Studio 2015
Now add another file to the project and name it bower.json. It should have following content:
{
"name": "MovieList",
"version": "0.1.0",
"authors": [
"Ravi Kiran"
],
"description": "A sample movie list app",
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular": "~1.3.11",
"bootstrap": "3.3.2"
}
}
Even in this file, you will get intellisense when you type-in the dependencies.
Figure 4: Dependencies Intellisense in bower.json
VS 2013 doesn’t have a menu to install Bower components, but VS 2015 has it. In VS 2015, you can install the Bower packages using Restore Packages option in the context menu in Bower option. I prefer installing the Bower packages using Grunt instead. Right click on the application and choose Add > New Item. From the menu, choose Gruntfile.js if available, otherwise add a new JavaScript file and name it Gruntfile.js. Add the following content to this file:
module.exports = function (grunt) {
'use strict';
grunt.initConfig({
// read in the project settings from the package.json file into the pkg property
pkg: grunt.file.readJSON('package.json')
});
//Add all plugins that your project needs here
grunt.loadNpmTasks('grunt-bowercopy');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-clean');
};
Here we are loading all available grunt tasks and storing the contents of the package.json file in an object for further use.
To install Bower components using Grunt, we need to use the grunt-bowercopy task referred above. At times, we may not want to have all Bower dependencies in the default folder. In such cases, we can move these files to a place of our choice by setting paths for each of the dependency. Following snippet shows the Grunt configuration of this task, it has to be added as a property to the object passed to grunt.initConfig method in the snippet above:
bowercopy: {
options: {
runBower: true,
destPrefix: 'public/libs'
},
libs: {
files: {
'angular': 'angular',
'jquery': 'jquery/dist',
'bootstrap': 'bootstrap/dist/css'
}
}
}
Note: In VS 2015, modify the value of destPrefix to wwwroot/lib, as all static files are referred from wwwroot by default
This task will copy the libraries into the folder libs under public folder.
Though we create a number of files while writing code, we ship just one file containing all the code in a minified format to optimize the network calls by browsers. For the movie list application, I created 3 different JavaScript files. Let’s write Grunt tasks to concatenate these files. The files can be minified using grunt-contrib-uglify task, I am leaving it as an assignment to the reader. Alternatively you can also read Using Grunt.js to Merge and Minify JavaScript files in an ASP.NET MVC Application.
Let’s concatenate the files using the Grunt task grunt-contrib-concat. Following is the Grunt configuration for this task, it concatenates the three files and stores the resultant file in another folder.
concat: {
options: {
separator: ';'
},
dist: {
src: ['app/moviesApp.js', 'app/moviesCRUDSvc.js', 'app/MoviesCtrl.js'],
dest: 'app/combined/moviesCombined.js'
}
}
It is a good practice to have all deliverables at one place to make the deployment easier. So let’s copy the combined JavaScript file and the CSS file to a single folder. It is done using the grunt-contrib-copy task. Following is the configuration:
copy: {
main: {
expand: true,
flatten: true,
filter: 'isFile',
src: ['app/combined/*.js','styles/*.css'],
dest: 'public/dist/'
}
}
Note: In VS 2015, modify the value of destPrefix to wwwroot/dist
The option flatten: true is used to avoid creation of folder structure under the target folder.
After copying the combined JavaScript file, we can remove it from the place where it was created. Following is the clean task that does this:
clean: ["app/combined/"]
We need these tasks to run sequentially. So let’s combine them in the right order into one task. Following is the task:
grunt.registerTask('default', ['bowercopy:libs', 'concat:dist','copy:main', 'clean']);
Here, default is the alias task that runs a number of other tasks internally.
Finally, to run the unit tests using Karma, we need to add a task. Following is the task for it:
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
Now we are done with configuring the tasks. Let’s run the tasks and see how it behaves. To run the task, open Task Explorer in Visual Studio through View > Other Windows > Task Explorer.
As you see, the task explorer is smart enough to detect the Grunt file and list all tasks and alias tasks configured. Right click on the default task and click run. This will run all the tasks under default, which means, it will run bower, copy libraries, concatenate, and copy the files to the specified location. Figure-5 shows output of an instance of the execution:
Figure 5: Task Runner Explorer
Now run the application using F5 or Ctrl+F5. You will be able to see the page loading in the browser.
As we didn’t include karma in the default task, it didn’t run tests. We can run karma using the karma node in the Task Runner Explorer. Figure-6 shows an instance of running karma:
Figure 6: Karma Unit tests
Using Gulp and Bower in Visual Studio
Gulp is an alternative to Grunt. It uses a different approach to solve the same problem. As already mentioned, Gulp uses node streams. Each task in Gulp produces a stream that can be piped into another task to continue execution. This approach is comparatively efficient than the approach used by Grunt, as it doesn’t need to store results physically to make it available to the next task.
Also, being programmers we prefer calling functions over writing configuration objects. In Gulp, we write chained function calls (as we do in LINQ).
Contents of the bower.json file would remain the same as in case of Grunt. Contents of package.json would change to include Gulp task packages. Following are contents of the package.json file:
{
"name": "Movie-list",
"preferGlobal": true,
"version": "0.1.0",
"author": "Ravi Kiran",
"description": "A sample movie list application",
"bin": {
"package-name": "./bin/package-name"
},
"dependencies": {
},
"analyze": false,
"devDependencies": {
"karma": "^0.12.31",
"karma-jasmine": "^0.3.5",
"gulp": "^3.8.10",
"gulp-bower": "^0.0.10",
"gulp-concat": "^2.4.3",
"gulp-karma": "^0.0.4"
},
"license": "MIT",
"engines": {
"node": ">=0.6"
}
}
Install these packages using the Visual Studio context menu options discussed in the previous section.
Add a new JavaScript file to the project and name it Gulpfile.js. This name is a convention, we can’t change the name. To start with, load all required NPM packages to this file:
var gulp = require('gulp');
var bower = require('gulp-bower');
var concat = require('gulp-concat');
var karma = require('gulp-karma');
Let’s install bower packages using the gulp-bower package. Following snippet creates a gulp task using this package to install all bower components inside bower_components folder:
gulp.task('bower', function () {
return bower('./bower_components');
});
Now that we have the front-end libraries, let’s move them to a different folder. For this, we need to set a destination for each of them. Following task does it:
gulp.task('copyLibs', ['bower'], function () {
gulp.src(['bower_components/angular/*.*'])
.pipe(gulp.dest('public/libs/angular'));
gulp.src(['bower_components/bootstrap/dist/css/*.*'])
.pipe(gulp.dest('public/libs/bootstrap'));
gulp.src(['bower_components/jquery/dist/*.*'])
.pipe(gulp.dest('public/libs/jquery'));
});
Note: In VS 2015, modify values passed into dest as wwwroot/lib, as all static files are referred from wwwroot by default
As you see, this setup is quite different from the setup we did in grunt. It is also a bit tricky to think in the terms of Gulp if you are used to Grunt for a long time. In the above snippet, gulp.src is a task and its result is passed to gulp.dest using pipe. As mentioned earlier, Gulp works on streams. gulp.src produces a stream and this stream is used by gulp.dest.
The second parameter in the above snippet is a list of tasks that have to run before this task runs. So whenever we run the copyLibs task, it internally runs the bower task.
Let’s combine the script files into one file using the gulp-concat task. As we can combine multiple tasks using pipes, we can copy the concatenated files to a target folder in the same task. Following is the task:
gulp.task('concat', function () {
return gulp.src(['app/moviesApp.js', 'app/moviesCRUDSvc.js', 'app/MoviesCtrl.js'])
.pipe(concat('public/dist/moviesCombined.js'))
.pipe(gulp.dest('.'));
});
The only independent task left is copying CSS file to the dist folder. This task is straight forward.
gulp.task('copyCss', function () {
return gulp.src(['styles/*.css'])
.pipe(gulp.dest('public/dist'));
});
Note: In VS 2015, modify values passed into dest as wwwroot/dist, as all static files are referred from wwwroot by default
Finally, we need to combine these tasks into one task. Following is the default task that combines all of these tasks:
gulp.task('default', ['copyLibs', 'copyCss', 'concat']);
To ensure correctness of the code, we should run unit tests too. Following task creates a task to run tests using karma:
gulp.task('karma', function () {
return gulp.src(['tests/*.js']).pipe(karma({
configFile: 'karma.conf.js',
action: 'watch'
}));
});
Task Runner Explorer in Visual Studio parses the Gulpfile.js file and lists all of the tasks configured as nodes using which we can directly run the tasks. Following figures 7 and 8 show instances of running the default task and the karma unit task respectively:
Figure 7: Running Default task
Figure 8: Running Karma Unit task
Conclusion
As you saw, Visual Studio has first class support for working with the front-end task runners. If you have kept yourself away from these tools till now, now is the time to start using them to get your job done and have an experience with the tools that everyone else is using. You will love it!
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active
blogger, an author at
SitePoint and at
DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community