Partager via


Using Bower with Visual Studio

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.

Migrating to Bootstrap 4

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

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

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"
  }
}

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

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 copy = require('gulp-copy');
var bower = require('gulp-bower');
var sourcemaps = require('gulp-sourcemaps');

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 (cb) {
    del([config.jquerybundle,
              config.bootstrapbundle,
              config.modernizrbundle], cb);
});

//Create a jquery bundled file
gulp.task('jquery-bundle', ['clean-vendor-scripts', 'bower-restore'], function () {
    return gulp.src(config.jquerysrc)
     .pipe(concat('jquery-bundle.min.js'))
     .pipe(gulp.dest('Scripts'));
});

//Create a bootstrap bundled file
gulp.task('bootstrap-bundle', ['clean-vendor-scripts', 'bower-restore'], function () {
    return gulp.src(config.bootstrapsrc)
     .pipe(sourcemaps.init())
     .pipe(concat('bootstrap-bundle.min.js'))
     .pipe(sourcemaps.write('maps'))
     .pipe(gulp.dest('Scripts'));
});

//Create a modernizr bundled file
gulp.task('modernizer', ['clean-vendor-scripts', 'bower-restore'], function () {
    return gulp.src(config.modernizrsrc)
        .pipe(sourcemaps.init())
        .pipe(uglify())
        .pipe(concat('modernizer-min.js'))
        .pipe(sourcemaps.write('maps'))
        .pipe(gulp.dest('Scripts'));
});

// Combine and the vendor files from bower into bundles (output to the Scripts folder)
gulp.task('vendor-scripts', ['jquery-bundle', 'bootstrap-bundle', 'modernizer'], function () {

});

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

gulp.task('css', ['clean-styles', 'bower-restore'], function () {
    return gulp.src([config.bootstrapcss, config.appcss])
     .pipe(concat('app.css'))
     .pipe(gulp.dest(config.cssout))
     .pipe(minifyCSS())
     .pipe(concat('app.min.css'))
     .pipe(gulp.dest(config.cssout));
});

gulp.task('fonts', ['clean-styles', 'bower-restore'], function () {
    return
    gulp.src(config.boostrapfonts)
        .pipe(gulp.dest(config.fontsout));
});

// Combine and minify css files and output fonts
gulp.task('styles', ['css', 'fonts'], function () {

});

//Restore all bower packages
gulp.task('bower-restore', function() {
    return 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")

to

 <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")

to

 <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")
}

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.

Comments

  • Anonymous
    February 17, 2015
    I understand the rationale behind using Bower, but I feel this is actually more work to set-up than using NuGet. I might be wrong?

    • Anonymous
      April 29, 2016
      I agree with you JS, I think nuget is less work than bower. I have had issues with references getting lost or misplaced, and nuget wasn't much help in updating the web config files in my project. If bower can handle that sort of issue I'd use it for those types of situations.
  • Anonymous
    February 17, 2015
    Great post!  There must be something in the air as at the same time you were writing this post I was writing one that covers very similar ground! blog.icanmakethiswork.io/.../using-gulp-in-asp-net-instead-of-web-optimization.html I too am using the Task Runner Explorer to plug my gulp task into the Visual Studio build.  It's working pretty well but unfortunately the app is sometimes served up before the gulp tasks have finished running.  Early days though.

  • Anonymous
    February 18, 2015
    I currently use the "main-bower-files" gulp plugin to move files from the "bower_components" directory. It works pretty well, for some packages I need to configure it in the "bower.json" like this: gist.github.com/.../c68370e7d9dd1c970a26 It works pretty nice.

  • Anonymous
    February 18, 2015
    I get that you would like to bundle and minify these files when you deploy them to production. But here you have removed all the debuggable files and just bundled and minified (MB) them. But how about when I debug locally and don´t like my files MB? Can you set up a "on debug do not MB" and "on relase do MB them"? Do you have that control?

  • Anonymous
    February 18, 2015
    @JS you are correct. It currently is more work than using Nuget. This is improving a lot in VS 2015. You can try out the VS 2015 preview to see how the tooling is improved. You no longer need to worry about installing Node, Git and all the other tools needed to make it work. All those tedious steps are handled by the Visual Studio web tooling.

  • Anonymous
    February 18, 2015
    @Sturla, I am working on some follow up posts to address your questions.

  • Anonymous
    February 22, 2015
    Thanks Dave I have been playing around with this all weekend (and loving it) and have been trying to publish to Azure but that just fails miserably as the publish doesn´t wait for the gulp to finish its thing and publishes what is has at that time. So I would love if you could add that to your follow up post if you think (like me :-)) it is heavily related.

  • Anonymous
    February 22, 2015
    @Sturla I have not seen this problem myself but I will look into it. I assume you are trying to publish to Azure directly from within Visual Studio using the Publish dialog?

  • Anonymous
    February 22, 2015
    Yes Dave and then just next,next, publish. When the publishing is in progress and the page opens up I'm still getting Gulp notifications and some files don´t seem to be uploaded because they (probably) were in deleted/changed state.

  • Anonymous
    February 23, 2015
    @Sturla, I have posted a workaround for the publishing issue here(www.davepaquette.com/.../waiting-for-gulp-tasks-to-finish-in-visual-studio.aspx) Also, I noticed that there were some errors in the gulpfile originally published in this post. Please see www.davepaquette.com/.../using-bower-with-visual-studio-2013.aspx for an updated version.

  • Anonymous
    March 04, 2015
    Timely article! I've been playing with VS2015 & ASP.NET 5 and was wondering how to start making use of bower and the gulp/grunt task runner stuff in my .Net 4.5 projects. I'm new to bower & gulp but NuGet is a frustrating experience for managing client-side js libraries so very happy Microsoft are heading this direction. I'm also interested in the questions Sturla raised re debug/production.

  • Anonymous
    March 25, 2015
    It's quite a horrible experience and bower alone is a tool I could easily live without. I attempted to use it and it's bringing down the entire GIT tree of a particular tag for a library. Where is the intelligence in that? Oh yes, I know the answer to this one, it handles dependencies i.e. clones dependent git repos as well. Yuck.

  • Anonymous
    May 22, 2015
    What happens with all the caching system that was provided by the "old" bundling system? Looks like that was left out of the article and was one of the pillars of the whole bundling system... This whole "new" system looks cumbersome and worst than the old one... I wouldn't mind to install files by hand instead of having to go through this thing for configuring every thing that I download and use on my project...

  • Anonymous
    May 27, 2015
    @Cesar The caching piece is a bit of a challenge in VS 2013. I am solving this using a fingerprinting technique outlined here: madskristensen.net/.../cache-busting-in-aspnet Luckily this is extremely easy in ASP.NET 5 using the Link and Script tag helpers: www.davepaquette.com/.../link-and-script-tag-helpers-in-mvc6.aspx

  • Anonymous
    June 07, 2015
    Hi David! One question: Do you add the output files to source control? (i.e. Content/dist/css/app.min.css)

  • Anonymous
    June 07, 2015
    Hi Fernando, In this case I did not check in the source files to source control. if you don't check it in, then you need to make sure your build server is able to run the gulp task for you. You can see an example of that here -> www.davepaquette.com/.../integrating-gulp-and-bower-with-visual-studio-online-hosted-builds.aspx David

  • Anonymous
    August 09, 2015
    David, with all due respect, it is a nightmare to work with this new system. Especially in a team. The workflow is a major step back for even common packages such as Bootstrap (!) - if you want any kind of clean output from a Bower, you need to examine each package, and usually manually add an override to its 'main' files as described by its bower.json - if it even has one! Most don't. It is, in practice, just a Git clone. Now, take the output from multiple Bower packages, and try to make that work with the current Google guidelines for PageSpeed. I.E Take the different javascript, CSS, images, fonts from the packages, and try to Uglify / Minify them, concat them, IN THE CORRECT ORDER SO THEY WORK. This is the real world stuff that is not covered in your article, and to be honest, in any article I have seen, that tells the story of how to use Here is a challenge - try making the 'isotope' Bower package work in any realistic workflow, without giving up and just copying the isotope/dist/isotope.pkgd.min.js file somewhere and cursing the two days you just wasted reading how everyone is stuck in a whirlpool of blame between NPM, Bower, Gulp / Grunt etc, and doesn't know what do. Often you need to add multiple Gulp packages to patch or workaround the strangeness of people not being able to follow 'guidelines' for their packages which are not enforced, or bugs, failures, or deprecations of the Gulp packages you actually wanted to use. Use 'run-sequence' is one advice - oh, 'run' is deprecated, and its dependant on that. Talking of dependencies, try checking out how deep the folder structure is for some packages in this mess - see if you can find the package that has empty folders including /node-modules/deep/node-modules/deeper/node-modules/deepest - hint, there are alreadyabout 100 other folders above it. Insane. Currently, there are 22,830 Files, and 4,843 Folders in my node_modules directory. That is for the 19 Gulp packages required just to get back to where I was with .Net bundling and minification. Except without the automatic production only minification. Or versioning. Or CDN with local failover support. Visual Studio 2015 is trying very hard, but it is putting lipstick on a pig. People will simply give up on this, copy the files they need from Github and give up on package management. I note that even Mads Kristensen, has released a new Bundling and Minification package for VS2015. A quote from someone I saw on StackOverflow yesterday: "The concept that you should dirty up your root directory for a package manager is maddening. Sigh. But I'm giving it a go anyways (despite this being like the 10th project I've used Bower on, I still am not really seeing a ton of benefit to using it)."

  • Anonymous
    August 16, 2015
    Check this stackoverflow.com/.../32035963

  • Anonymous
    September 08, 2015
    This one does not work : =============== // Synchronously delete the output style files (css / fonts) gulp.task('clean-styles', function (cb) {    del([config.fontsout, config.cssout],cb); }); =============== But this works: // Synchronously delete the output style files (css / fonts) gulp.task('clean-styles', function () {    return del([config.fontsout, config.cssout]); });

  • Anonymous
    October 28, 2015
    The comment has been removed

  • Anonymous
    November 12, 2015
    Not sure if anyone can help me here, but I have a couple of questions about Bower integration:

  1. Are there plans to upgrade the version of Bower to something later?  The version Visual Studio uses is pretty old...
  2. We are planning to define some Bower packages that will not be registered in the public Bower repository.  Is there any way to update the intellisense data used in the bower.json editor?  That is, we would like to have our custom packages show up in the list as a user adds dependencies.