Introduction
We’ve created our web project more than a year ago having solid experience with NuGet and other Microsoft technologies. Generally, it’s a Single Page Application (SPA) with ASP.NET Web API and AngularJS. Using well known NuGet for all the dependencies management seemed to be the straightforward way. We referenced jQuery, AngularJS and bunch of other client-side packages in it. Then added some more packages manually as they were missing or outdated in NuGet. The time was passing and finally we had to admit that client-side dependencies management was real pain.
This article is about the migration to Bower packages manager and Gulp task runner in VS.NET web project and based on real world case.
Package adding procedure before and after the migration
We‘ve been using .NET “ecosystem” for a long time, but had many problems with the client-side packages management while using Nuget. Most of the needed packages were missing and only few were up to date. A new package installation routine was to:
-
Find the source code of needed functionality. In most cases it was packaged and available on github.
-
Download and unpack the zip from github.
-
Read the installation instructions on github if provided. Had to skip all the Bower/NPM/node.js/whatever else package manager installation instructions and continue to search for sample with all the required file includes listed. (Yes, didn‘t even know about the bower.json file with the main files list.)
-
Find, copy the required files to web application folder and include them to visual studio project. (angularjs and Angular.UI.Utils packages contain hundreds of files.)
-
We’re using Web Optimization bundles for css/js includes, so had to add references to the stylesheet and JavaScript files in appropriate bundles in BundleConfig class.
-
Reference the new angularJS module in the application and include the new functionality/behavior to the right file.
-
Restart IIS application pool, recompile the web application.
-
Test the resulting website on the browser. If any issue occurred, had to back-track the steps taken. Common resolution was to repeat the manual file adding routine as some more files were missing.
A package upgrade was done rarely as it included most of the manual installation steps. Visual Studio 2015 was a long awaited product for its Bower and Gulp support out of the box. The current procedure for adding a new package:
-
Find the source code of needed functionality. In most cases it’ll be available as Bower package.
-
Open bower.json and start typing the desired package name. Visual Studio autocomplete will do the rest: complete the package name and suggest the package version. The package will be downloaded almost immediately after saving the bower.json.
-
Open Task Runner Explorer (View » Other windows » Task Runner Explorer) and run our custom “packAll” task. (We’ll dive deeper into the tasks in “Gulp tasks” section later.)
-
Reference the new angularJS module in the application and include the new functionality/behavior to the right file.
-
Test the resulting website on the browser. If any issue occurred, check the package documentation for details. At this point we can be sure that the package content is loaded, just need to check configuration or components usage documentation.
The manual “packAll” job running could be made automatic by watching the file changes, but we left it manual on purpose for several reasons:
-
We don’t want any additional background processes as the whole module change procedure is manual anyway.
- The resulting script/style files might be marked as updated even though there were no real changes. We don’t want any accidental changes in our git repository history.
Installing Bower and the required packages
Our web project was created in mid-2014 using the latest MVC site template available at that moment. That’s why the Solution Explorer looks a little bit different than you can see in other tutorials where new projects (from new templates) are created.
-
Right click on the web project's name and choose Add » New Item…
-
Select Visual C# » Web » General and choose Bower Configuration File from the list:
-
Leave the file name as bower.json and click Add.
-
Notice that a Bower configuration file .bowerrc is also created. It contains single entry directory that specifies the path that Bower uses to store downloaded packages. We change the default value to client_packages:
-
Go back to bower.json. Start typing "jquery" in the dependencies node and see the IntelliSense springing out:
That way we fill the whole dependencies list:
-
Most of the dependent packages depend on angularJS and they reference various versions of it. We resolve the dependencies to current version of angularJS as described in Bower Resolutions blogpost:
-
Also we need to load jquery and jquery-ui before angular is loaded. We achieve this by overriding angular package dependencies:
-
After saving the bower.json file, the “Bower.cmd install --force-latest” command is executed in the background and “Installing packages complete” message appears in Visual Studio status bar after a while.
Bower configuration is complete and the downloaded packages are available in client_packages folder.
Installing Gulp and plugins
At this point we have all the required packages collected, but the files are scattered throughout the client_packages folder and it’s still cumbersome to reference them in HTML. Doing it manually would be highly ineffective, error prone and nightmare to manage.
Fortunately, there are task runners available and they are accessible right in Visual Studio 2015. We chose to use Gulp as the ASP.NET project templates use it by default. Not much can be done having plain Gulp only. The huge number of plugins gives the power to Gulp. Installation steps:
-
Right click on the web project's name and choose Add » New Item…
-
Select Visual C# » Web » General and choose NPM configuration file from the list.
-
Leave the file name as package.json and click Add. The file is added to the project with some default content:
-
Add the packages to devDependencies node to make them available in the project. We include:
-
gulp - gulp task runner itself;
-
gulp-concat - files concatenation utility;
-
gulp-uglify - JavaScript files minification utility;
-
main-bower-files - a utility for picking only required files from bower packages.
The complete NMP configuration file that we use in platform main web project:
-
After saving the package.json file, the packages installation process is executed in the background and “Installing packages complete” status message appears again.
Gulp and the utilities are ready for action!
Gulp tasks
In order to configure and run the actual gulp tasks we’ll create yet another file where the Bower packages processing takes place and the final outcome is produced.
-
Right click on the web project's name and choose Add » New Item…
-
Select Visual C# » Web » General and choose Gulp Configuration file from the list.
-
Leave the file name as gulpfile.json and click Add. The file is added to the project with some default content. We replace it with the following:
var gulp = require("gulp"),
mainBowerFiles = require('main-bower-files'),
concat = require("gulp-concat"),
uglify = require("gulp-uglify");
gulp.task('packJavaScript', function () {
return gulp.src(mainBowerFiles({
filter: /.*\.js$/i
}))
.pipe(concat('allPackages.js'))
.pipe(uglify())
.pipe(gulp.dest('Scripts'));
});
gulp.task('packCss', function () {
return gulp.src(mainBowerFiles({
filter: /.*\.css$/i
}))
.pipe(concat('allStyles.css'))
.pipe(gulp.dest('Content'));
});
gulp.task('copyMainFonts', function () {
return gulp.src(mainBowerFiles({
filter: /.*\.(eot|svg|ttf|woff)$/i
}))
.pipe(gulp.dest('Content'));
});
gulp.task('fontawesomeCss', function () {
return gulp.src('client_packages/font-awesome/css/font-awesome.css')
.pipe(gulp.dest('Content/themes/main/css'));
});
gulp.task('fontawesomeFonts', function () {
return gulp.src('client_packages/font-awesome/fonts/*.*')
.pipe(gulp.dest('Content/themes/main/fonts'));
});
gulp.task('fontawesomePackage', ['fontawesomeCss', 'fontawesomeFonts']);
gulp.task('packAll', ['packJavaScript', 'packCss', 'copyMainFonts', 'fontawesomePackage']);
We’ve made the tasks as universal and simple at the same time as possible. Adding a new package should NOT require any additional activities. As you can see, the only exception that we have is font-awesome package. It includes fonts and uses them from CSS situated in another folder. Because of this special structure the font-awesome dedicated tasks were created.
-
Right click on gulpfile.js in Solution Explorer and select Task Runner Explorer. You’ll see the tasks list on the left. Double click “packAll” node to run it. The task execution progress opens on the right:
The task finishes shortly with the following artifacts produced:
-
allPackages.js – minified JavaScript from all the packages;
-
allStyles.js – CSS from all the packages;
-
CSS and font files from font-awesome package copied to Content/themes/main folder;
-
all other (if any) font files copied to Content folder.
The references switch
We have to replace all the previously manually (or through Nuget packages) referenced files with the ones produced in previous step.
-
Uninstall all AngularJS and jQuery related packages through Nuget;
-
Delete all CSS, js files from Content, Scripts (or any other) folders that duplicate the packages content added through Bower;
-
Remove the references to deleted files from BundleConfig class. In our case nearly 60 such references were removed.
-
Include the 9 produced files into the project:
Note that client_packages and node_modules folders (containing Bower and NPM packages respectively) were NOT included to the project. They can be considered as “containing temporary data” and are excluded from source control too. (Keeping the github repository clean could prevent it from growing to enormous size. By the way, we had this problem before and dealt with it in previous article.)
- Reference the 3 newly added (font-awesome.css, allStyles.css and allPackages.js) files in BundleConfig class. We are using System.Web.Optimization for bundles registration as we need to gather the bundles at runtime. Our platform solution provides the ability to install/uninstall modules on the fly. Each such module can contain client-side resources that need to be loaded. After module installation the web application is restarted and resources from current modules are registered dynamically at startup.
Results
-
A clear, complete and always up to date list of client packages that the project uses;
-
Easy packages management (including package version upgrade/downgrade);
-
Ability to use a mature build tool for client side scripts preprocessing;
- More than 800 (!) files removed from platform web project during the references switching process and only several new ones added. As a side effect of the migration, our project was cleaned from redundant and unused files.
Conclusion
If your web project started years ago and you’ve just switched to Visual Studio 2015, take advantage of the new task runners feature and make your client-side development easier. (Even if you’re still on Visual Studio 2013, try out the Task Runner Explorer extension.) It’s time to start managing your client-side dependencies effectively and fully automate JavaScript and CSS files preprocessing and referencing in HTML. We’ve described in detail how we did migration to Bower package manager and Gulp task runner in our project, but hopefully you can follow the steps described and do the same in your project as well.
An impressing number of files have been deleted during this migration in our project. How many files will you get rid of?