Using NPM and Gulp in Visual Studio 2017

By default new Web Applications created in Visual Studio 2017 use Bower for managing external libraries such as jQuery and Bootstrap, however Bower is no longer best practice (and seemed a bit flaky in Visual Studio anyway) and so for new projects I now use npm for managing packages.

Using npm in Visual Studio takes a bit more work than Bower to setup but is better supported and gives more control over the library files that are published.

Preparation

As per this article it’s easier to use npm from the command line and installing the extension “Open Command Line” means you can open a command prompt scoped to any folder in your solution from a right click.

npm

Open a command prompt scoped to your project folder and run npm init to initialise npm in your project, this will ask you for some basic details about your project but you can just accept the defaults and edit the values later in the configuration if you’d like.

Once npm has been initialised you can either add a configuration file manually and start adding packages from there or install a package from the command line and have it create the configuration file for you which is what I usually do, to install jQuery use npm install jquery --save.

The package.json file will now be added to your project and the folder node_modules will be created and jQuery downloaded into it. By default node_modules will be hidden so to view it you’ll need to enable the “Show All Files” option in the solution explorer.

Your package.json file will look something like that below with descriptive values set from the initialisation command and dependencies on any packages that you’ve installed.


{
  "name": "FileUpload",
  "version": "0.0.0",
  "description": "",
  "main": "gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "azure-storage": "^2.8.1",
    "bootstrap": "^4.0.0",
    "gulp": "^3.9.1",
    "gulp-concat": "^2.6.1",
    "gulp-uglify": "^3.0.0",
    "jquery": "^3.3.1",
    "jquery-validation": "^1.17.0",
    "jquery-validation-unobtrusive": "^3.2.8",
    "merge-stream": "^1.0.1",
    "popper.js": "^1.14.0",
    "rimraf": "^2.6.2"
  }
}

Installing the desired packages into the node_modules folder is fine, but as this is outside of the projects wwwroot folder there needs to be some way to access it from the running web application, this is where gulp comes in.

There is also a quick and temporary solution for making the node_module folder visible as detailed here which uses the AddStaticFiles middleware to add node_modules in the same way that wwwroot is added.

This is done by adding a second AddStaticFiles middleware in the Configure method of Startup.cs like so.


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseBrowserLink();
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/Home/Error");
	}

	// For wwwroot directory
	app.UseStaticFiles();

	// Add support for node_modules but only during development **temporary**
	if (env.IsDevelopment())
	{
		app.UseStaticFiles(new StaticFileOptions()
		{
			FileProvider = new PhysicalFileProvider(
			  Path.Combine(Directory.GetCurrentDirectory(), @"node_modules")),
			RequestPath = new PathString("/vendor")
		});
	}

	app.UseMvc(routes =>
	{
		routes.MapRoute(
			name: "default",
			template: "{controller=Home}/{action=Index}/{id?}");
	});
}

These can then be referenced in your project from the vendor directory as you would with those from the wwwroot directory.


<link rel="stylesheet" href="~/vendor/bootstrap/dist/css/bootstrap.css" />
<script src="~/vendor/jquery/dist/jquery.js"></script>

Gulp is a better solution to this problem as rather than just copying the whole node_modue folder when the project is published it can be used to only copy specific files as well as to perform actions such as minification upon them.

Gulp

I got gulp working by using this guide as well Shawn Wildermuth’s article linked to above.

The first step is to install gulp using npm from a command line scoped to the project folder.


npm install gulp --save

Various plugins can then be installed to add additional features.

The ones installed in the package.json file above are used in this example from Shawn Wildermuth which I’ve used as the basis of my gulpfile.js below.

The file gulpfile.js can be added to your project by right clicking and adding a new item.

This file contains javascript which determines what files are copied and where to as well as the required transformations.


/// <binding BeforeBuild='default' />
/*
This file is the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. https://go.microsoft.com/fwlink/?LinkId=518007
*/

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var rimraf = require("rimraf");
var merge = require('merge-stream');

gulp.task("minify", function () {

    var streams = [
        gulp.src(["wwwroot/js/*.js"])
            .pipe(uglify())
            .pipe(concat("site.min.js"))
            .pipe(gulp.dest("wwwroot/lib/site"))
    ];

    return merge(streams);
});

// Dependency Dirs
var deps = {
    "azure-storage": {
        "lib/**/**/**/*": ""
    },
    "bootstrap": {
        "dist/**/*": ""
    },
    "jquery": {
        "dist/*": ""
    },
    "jquery-validation": {
        "dist/**/*": ""
    },
    "jquery-validation-unobtrusive": {
        "dist/*": ""
    },
    "popper.js": {
        "dist/**/*": ""
    },
};

gulp.task("clean", function (cb) {
    return rimraf("wwwroot/vendor/", cb);
});

gulp.task("scripts", function () {

    var streams = [];

    for (var prop in deps) {
        console.log("Prepping Scripts for: " + prop);
        for (var itemProp in deps[prop]) {
            streams.push(gulp.src("node_modules/" + prop + "/" + itemProp)
                .pipe(gulp.dest("wwwroot/vendor/" + prop + "/" + deps[prop][itemProp])));
        }
    }

    return merge(streams);

});

gulp.task("default", ['clean', 'minify', 'scripts']);

To test that the code in gulpfile.js is working as intended you can right click on it and select “Task Runner Explorer”, this will open a window allowing you to run each function manually as well as to bind them to project events so that it will run before each project build for example.


4 Comments

Laurie Dickinson · 27th September 2018 at 2:51 pm

Thanks, this is very helpful and quite clear. One question: What is the index.js referenced in the package.json file. Does that do anything or does gulp just look for the gulpfile.js file?

    Shinigami · 27th September 2018 at 3:37 pm

    Hmm, I think that must have come from the article that I initially got this from when researching this integration. Looking at my current projects the main value is gulpfile.js which is what I’d expect, I’ll update the article accordingly. Good spot!

Kevin Biasci · 12th October 2018 at 12:07 am

Thanks a lot for this, I was in the process of removing all the Bower references from my project and replacing them with LibMan/NPM. You saved my day 🙂

ROGÉRIO MORALEIDA · 16th November 2018 at 8:23 pm

Tanks.. Plase send demo in github.

Leave a Reply

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