Dave Paquette

Caffeine Driven Development

Using Bower with Visual Studio 2013

December 27, 2014

You may have heard that with ASP.NET 5 and Visual Studio 2015, Microsoft will be using Bower as a client-side package manager. That means that packages like jQuery, Bootstrap and Angular will no longer be referenced using NuGet. This might scare you initially, but I very strongly believe that this is a good thing.

Why is this a good thing?

NuGet is a great package manager for .NET libraries and the Microsoft eco-system. The web however, is much larger than Microsoft. The web development world has largely settled on Bower as the defacto package manager for client side libraries. We can’t expect a developer who creates a fancy new JavaScript library to publish their library as a NuGet package. That developer might have nothing to do with the Microsoft ecosystem and expecting them to learn it so we can use their package is just not reasonable. This brings us to the current state of client side packages on Nuget. Many packages are not available on Nuget. Equally as frustrating, some packages are available but are horribly out of date. This seems to be getting worse lately.

So…Given the fact that Microsoft is moving to Bower in the next version of ASP.NET, I have started migrating all my existing ASP.NET projects to use Bower for client side packages.

Here is a step-by-step guide to using Bower with Visual Studio 2013 using the MVC5 File->New Project template as an example.

Install Node and Bower

If you don’t already have it installed, download and install node.js.

Once node is installed, we need to install bower using the node package manager (npm). From the command line, run

npm install bower -g

Install Git

Yup..you’re going to need git. Bower uses git to download packages from github. Specifically, you will need to install msysgit and select the “Run Git from the Windows Command Prompt” option.

msysgit

Source: https://github.com/bower/bower

Initializing Bower for our project

First up, we will need to create a new bower.json file for our project. This is the file that will contain a list of all the packages your application depends on. The easiest way to do this is to run the bower init command in the your project folder:

image

The default settings are fine for our purposes. The only setting I changed was marking the project as private to avoid accidentally publishing my project to the public bower registry.

Replacing Nuget Packages with Bower Packages

A new MVC 5 project reference jQuery, jQuery Validation, Bootstrap and Modernizr. Let’s start by adding jQuery using Bower. Bower packages are installed from the command line using the bower install.

bower install jquery --save
bower install jquery --save

image

 

By default, bower components are installed in the bower_components folder. I don’t include this folder in Visual Studio and I don’t check it in to source control. The contents are easily restored by calling the bower install command with no other options.

image

Next, we will install the remaining packages. We also need to install RespondJS. This is a responsive design polyfill that is included in the Nuget bootstrap package but is not included in the bower package.

bower install jquery-validation --save
bower install jquery-validation-unobtrusive --save
bower install modernizr --save
bower install bootstrap --save
bower install respond-minmax --save
bower install jquery-validation --save
bower install jquery-validation-unobtrusive --save
bower install modernizr --save
bower install bootstrap --save
bower install respond-minmax --save

Now, we have all our required client side components installed in the bower_components folder. The bower.json file describes all the client side packages that our project depends on.

{
  "name": "BowerInVS2013",
  "version": "0.0.0",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "jquery": "~2.1.3",
    "modernizr": "~2.8.3",
    "jquery-validation": "~1.13.1",
    "bootstrap": "~3.3.1",
    "respond-minmax": "~1.4.2",
    "jquery-validation-unobtrusive": "~3.2.2"
  }
}
{
  "name": "BowerInVS2013",
  "version": "0.0.0",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "jquery": "~2.1.3",
    "modernizr": "~2.8.3",
    "jquery-validation": "~1.13.1",
    "bootstrap": "~3.3.1",
    "respond-minmax": "~1.4.2",
    "jquery-validation-unobtrusive": "~3.2.2"
  }
}

 

Getting files from bower_components

Some people will simply include the bower_components folder into the web application and reference the JavaScript / CSS files directly. A typical bower_components folder contains much more than we actually need. I prefer to copy only the files we need to a known location and reference them from there. This is a task that can be easily accomplished using a client side build system like Grunt or Gulp.

I prefer using Gulp these days so that’s what we will use here. To setup our project for Gulp, follow the instructions on my earlier blog post.

We will need the following node modules for this gulp file.

npm install gulp --save-dev
npm install gulp-concat --save-dev
npm install gulp-uglify --save-dev
npm install del --save-dev 
npm install gulp-bower --save-dev 
npm install gulp-minify-css --save-dev
npm install gulp --save-dev
npm install gulp-concat --save-dev
npm install gulp-uglify --save-dev
npm install del --save-dev 
npm install gulp-bower --save-dev 
npm install gulp-minify-css --save-dev

 

Here is the gulpfile.js that I created to roughly mimic the bundles that are created by the MVC new project template. The comments in the file explain what each gulp task is doing.

// include plug-ins
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var del = require('del');
var minifyCSS = require('gulp-minify-css');
var bower = require('gulp-bower');
 
var config = {
    //JavaScript files that will be combined into a jquery bundle
    jquerysrc: [
        'bower_components/jquery/dist/jquery.min.js',
        'bower_components/jquery-validation/dist/jquery.validate.min.js',
        'bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js'
    ],
    jquerybundle: 'Scripts/jquery-bundle.min.js',
 
    //JavaScript files that will be combined into a Bootstrap bundle
    bootstrapsrc: [
        'bower_components/bootstrap/dist/js/bootstrap.min.js',
        'bower_components/respond-minmax/dest/respond.min.js'
    ],
    bootstrapbundle: 'Scripts/bootstrap-bundle.min.js',
 
    //Modernizr
    modernizrsrc: ['bower_components/modernizr/modernizr.js'],
    modernizrbundle: 'Scripts/modernizer.min.js',
 
    //Bootstrap CSS and Fonts
    bootstrapcss: 'bower_components/bootstrap/dist/css/bootstrap.css',
    boostrapfonts: 'bower_components/bootstrap/dist/fonts/*.*',
 
    appcss: 'Content/Site.css',
    fontsout: 'Content/dist/fonts',
    cssout: 'Content/dist/css'
 
}
 
// Synchronously delete the output script file(s)
gulp.task('clean-vendor-scripts', function () {
    del.sync([config.jquerybundle,
              config.bootstrapbundle,
              config.modernizrbundle]);    
});
 
// Combine and the vendor files from bower into bundles (output to the Scripts folder)
gulp.task('vendor-scripts', ['clean-vendor-scripts', 'bower-restore'], function () {
    gulp.src(config.jquerysrc)
     .pipe(concat('jquery-bundle.min.js'))
     .pipe(gulp.dest('Scripts'));
 
    gulp.src(config.bootstrapsrc)
     .pipe(concat('bootstrap-bundle.min.js'))
     .pipe(gulp.dest('Scripts'));
 
    gulp.src(config.modernizrsrc)
        .pipe(uglify())
        .pipe(concat('modernizer-min.js'))
        .pipe(gulp.dest('Scripts'));
 
});
 
// Synchronously delete the output style files (css / fonts)
gulp.task('clean-styles', function () {
    del.sync([config.fontsout,
              config.cssout]);
});
 
// Combine and minify css files and output fonts
gulp.task('styles', ['clean-styles', 'bower-restore'], function () {
    gulp.src([config.bootstrapcss, config.appcss])
     .pipe(minifyCSS())
     .pipe(concat('app.min.css'))
     .pipe(gulp.dest(config.cssout));
 
    gulp.src(config.boostrapfonts)
        .pipe(gulp.dest(config.fontsout));
 
});
 
//Restore all bower packages
gulp.task('bower-restore', function() {
    bower();
});
 
//Set a default tasks 
gulp.task('default', ['vendor-scripts', 'styles'], function () { });
// include plug-ins
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var del = require('del');
var minifyCSS = require('gulp-minify-css');
var bower = require('gulp-bower');

var config = {
    //JavaScript files that will be combined into a jquery bundle
    jquerysrc: [
        'bower_components/jquery/dist/jquery.min.js',
        'bower_components/jquery-validation/dist/jquery.validate.min.js',
        'bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js'
    ],
    jquerybundle: 'Scripts/jquery-bundle.min.js',

    //JavaScript files that will be combined into a Bootstrap bundle
    bootstrapsrc: [
        'bower_components/bootstrap/dist/js/bootstrap.min.js',
        'bower_components/respond-minmax/dest/respond.min.js'
    ],
    bootstrapbundle: 'Scripts/bootstrap-bundle.min.js',

    //Modernizr
    modernizrsrc: ['bower_components/modernizr/modernizr.js'],
    modernizrbundle: 'Scripts/modernizer.min.js',

    //Bootstrap CSS and Fonts
    bootstrapcss: 'bower_components/bootstrap/dist/css/bootstrap.css',
    boostrapfonts: 'bower_components/bootstrap/dist/fonts/*.*',

    appcss: 'Content/Site.css',
    fontsout: 'Content/dist/fonts',
    cssout: 'Content/dist/css'

}

// Synchronously delete the output script file(s)
gulp.task('clean-vendor-scripts', function () {
    del.sync([config.jquerybundle,
              config.bootstrapbundle,
              config.modernizrbundle]);    
});

// Combine and the vendor files from bower into bundles (output to the Scripts folder)
gulp.task('vendor-scripts', ['clean-vendor-scripts', 'bower-restore'], function () {
    gulp.src(config.jquerysrc)
     .pipe(concat('jquery-bundle.min.js'))
     .pipe(gulp.dest('Scripts'));

    gulp.src(config.bootstrapsrc)
     .pipe(concat('bootstrap-bundle.min.js'))
     .pipe(gulp.dest('Scripts'));

    gulp.src(config.modernizrsrc)
        .pipe(uglify())
        .pipe(concat('modernizer-min.js'))
        .pipe(gulp.dest('Scripts'));

});

// Synchronously delete the output style files (css / fonts)
gulp.task('clean-styles', function () {
    del.sync([config.fontsout,
              config.cssout]);
});

// Combine and minify css files and output fonts
gulp.task('styles', ['clean-styles', 'bower-restore'], function () {
    gulp.src([config.bootstrapcss, config.appcss])
     .pipe(minifyCSS())
     .pipe(concat('app.min.css'))
     .pipe(gulp.dest(config.cssout));

    gulp.src(config.boostrapfonts)
        .pipe(gulp.dest(config.fontsout));

});

//Restore all bower packages
gulp.task('bower-restore', function() {
    bower();
});

//Set a default tasks 
gulp.task('default', ['vendor-scripts', 'styles'], function () { });

To run the gulp tasks, simply run gulp from the command line. This should output a number of min.js files to the Scripts folder and some css and font files to the Contents/dist folder. In Visual Studio, include these new files into the project.

image

Removing NuGet Packages

Now that we have all the script and css bundles we need, we can uninstall the original NuGet packages.

Uninstall-Package Microsoft.jQuery.Unobtrusive.Validation

Uninstall-Package jQuery.Validation

Uninstall-Package Bootstrap

Uninstall-Package jQuery

Uninstall-Package Respond

Uninstall-Package Modernizr

 

The Script folder is now significantly cleaned up, only containing the files that are generated as output from the gulp build.

image

 

Referencing Scripts and Styles

Next, we need to change the way we are referencing the script and css files. In Views\Shared\_Layout.cshtml change the following in the head tag

    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

to

    <link rel="stylesheet" href="~/Content/dist/css/app.min.css">
    <script src="~/Scripts/modernizer-min.js"></script>
    <link rel="stylesheet" href="~/Content/dist/css/app.min.css">
    <script src="~/Scripts/modernizer-min.js"></script>

 

At the bottom of the page, change the following

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")

to

    <script src="~/Scripts/jquery-bundle.min.js"></script>
    <script src="~/Scripts/bootstrap-bundle.min.js"></script>
    <script src="~/Scripts/jquery-bundle.min.js"></script>
    <script src="~/Scripts/bootstrap-bundle.min.js"></script>

 

And finally, remove any reference to the jquery validation bundle since we included those scripts in the jQuery bundle.

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

To complete the cleanup process, we can delete the App_Start/BundleConfig.cs file and remove the call to RegisterBundles from Global.asax.cs.

Ensuring Gulp runs before running the app

Currently, our solution requires the developer to manually call gulp from the command line to restore bower packages and re-generate the output files.

As we did in my previous blog post, we can use the Task Runner Explorer to ensure these tasks will be run anytime the developer builds the application in Visual Studio.

Open the Task Runner Explorer window (View –> Other Windows –> Task Runner Explorer). Select the default gulp task, right click, select Bindings –> Before Build. Now the default gulp task will run every time the application is built in Visual Studio. This will ensure that the output files are always up to date for the developer.

image

Conclusion

We now have a fully working application that uses Bower to reference client side libraries and Gulp to define and generate bundles. By using Bower, we get access to a larger and more up-to-date library of client side packages than we did with NuGet. Using Gulp, we have a lot more flexibility in how we define bundles than we did with the ASP.NET Bundling / Minification system.

This approach is more consistent with the way the rest of the world is building web applications today and should make for an easier transition into the next version of ASP.NET.

Holiday Learning List

December 17, 2014

I am looking forward to a some rare free time over the holidays. I am hoping to watch some training videos and catch up on some blogging (I have a few unfinished posts that I would like to finish).

Here is a list of free Microsoft Virtual Academy courses that jumped out at me.

Cloud Courses

Microsoft Azure Fundamentals – Websites

Dev/Test Scenarios in the DevOps World

Enterprise Developer Camp Jump Start

Full List of Azure Courses

Web Development

Building Response UI with Bootstrap

Single Page Apps with jQuery or AngularJS

Full list of Web Dev Courses

 

Enjoy!