More specifically, Bower is a better package manager than NuGet for client-side (JavaScript, CSS, etc.) packages, even for ASP.NET web applications where the incumbent is very well entrenched.
Apples vs. Oranges?
Is this a fair comparison? Let me offer you my disclaimer. The only reason I’m making this particular comparison is because my background is in ASP.NET web applications where NuGet was the only package manager option for quite a while. (Which means you’re probably not going see a Rails developer blogging about this :). These days, however, your average .NET web app developer is highly likely to run into a dilemma between package managers during the course of application construction. The happy path of finding all the packages you needed from just one package manager quickly accelerates into a dirt road bumpy enough to get your apple cart all jacked up.Let me start by acknowledging that NuGet is and will continue to be the standard when it comes to .NET package management. I don’t anticipate that Bower will wholesale replace NuGet as they don’t set out to achieve the same goals. Overlap between these package managers does exist, however. NuGet hosts a variety of packages that do not add .NET assemblies to your project’s reference list, JavaScript frameworks and CSS libraries being the most popular in this category. Not only that, but some of these are actually the most popular packages hosted on NuGet.org, such as the jQuery package with now over 6 million downloads. The case I’ll be making here is that where NuGet and Bower overlap in the realm of client-side packages, Bower is a better choice.
NuGet encourages polluting global scope
What’s wrong with NuGet’s client-side packaging implementation then? In 3 words: Global scope pollution. Ever since NuGet first launched and we started using the jQuery package included in our ASP.NET web application templates, the infamous Scripts directory has always been the dumping ground of every JavaScript package. This directory, as well as the Content directory for CSS, has essentially become a global namespace of sorts. As soon as package authors recognize this “convention” contained in several of the most popular packages, they replicate this pattern and create their packages to deploy their JavaScript files into Scripts and their CSS files into Content. For example, here’s the scripts directory after creating a new project in Visual Studio 2013 and adding a couple Angular packages.What if 2 packages each want to deliver foo.js to Scripts? Well, I guess the last guy in wins in that case. The responsibility of avoiding name collisions is left solely with the package author.
In the defense of package authors continuing this practice, it seems to me that they are merely following Microsoft’s lead, since we have officially sanctioned ASP.NET web application project templates installed in Visual Studio that contain pre-installed NuGet packages which install their files directly into the Scripts directory. Does Microsoft own these packages? No. They are quick to point out that you assume all risk of using 3rd party packages. But what happens when said 3rd party packages are pre-installed in the project template? While we could all agree this is a gray area, ultimately I place the responsibility of enforcing a polite ecosystem on the package manager. Ideally, a package manager should discourage these types of collisions to whatever extent is practical and appropriate.
It’s also worth pointing out that the Scripts and Content folders are merely secondary delivery locations. Behind the scenes, as shown in the following screenshot, all packages are isolated quite nicely in the packages folder.
The Bower alternative
Now let’s take a look at what Bower has to offer.Run the following in your console of choice.
bower install <package name here>
What? Did you get an error? bower is not a recognized command? You need to install it first, then. :) Once installed, this command will produce a bower_components directory with a folder for each package. For example, here’s a screenshot of my bower_components directory.So, that’s pretty much it. What else does Bower do for us? Not a thing.
NuGet violates the principle of Separation of Concerns
So why is Bower “better” then? Another 3 words: Separation of Concerns. The Bower team decided that a package manager’s responsibilities should not include delivery into your downstream application. This is essentially the software development principle of Separation of Concerns, package manager style. By definition and design, Bower will never have the global namespace issue that NuGet is currently suffering from because it has no intentions of offering the consumer the convenience of editing their application’s XML project file (csproj).I must confess that editing a csproj file is a nice feature to have, since csproj files (or any other .NET XML project file) are the gatekeepers for all sorts of downstream delivery concerns in the .NET world, such as automated builds and deployments. While still new and emerging, there are highly productive alternatives developing in the form of Yeoman and also gulp that I believe will eventually creep into the space of csproj manipulation to assist the .NET web application developer. At the very least, I would encourage you to start evaluating them.It may appear at first glance that since NuGet has more features and tighter integration with Visual Studio, it must be a superior choice in the world of .NET web app developers. However, in the real world of closer inspection, these powerful features can actually harm your project.
An example collision failure
Allow me to submit to you an example of what could go wrong with global namespace pollution (and did go wrong on one of my projects). I will pick on the AngularJs.Core package because of its popularity. This package emits files (angular.js being the primary one) into the Scripts folder just like the official AngularJs package. I think I understand the intentions behind why AngularJs.Core was created, which is that you only want the core files required to run Angular and not all of the other optional submodules (and there are quite a few). I would even go so far as to agree that it would be nice if the AngularJs package was layered in this fashion, as this is a commonly accepted practice in NuGet already. I’m only an outsider here speculating, however. I don’t know the full history of these packages. At this point, it seems confusing to have an official and unofficial package with the same deliverable, icon and description, both of which having thousands of downloads and a large set of downstream dependent packages.Can you tell at first glance which one is the official package and which one is a copy? They both look rather official to me.
So what? What could possibly go wrong? Take a look at the diagram below. Let’s say that I have the official AngularJs package installed and then I decide to add a couple of new Angular packages. One of them (Package B in the diagram below) just so happens to have a package dependency defined against AngularJs.Core.
When Package B is installed, NuGet installs all dependent packages automatically (if they’re not already installed), which installs AngularJs.Core and overwrites angular.js in Scripts. Perhaps this happens on a day when a newer version of Angular is released and you had already installed it (Let’s say, 1.2.18). Installing Package B will overwrite angular.js 1.2.18 with angular.js 1.2.17 contained in AngularJs.Core because perhaps its authors haven’t had a chance to update it yet to the latest version.
Occasionally you’ll get a warning prompt about files that will be overwritten by packages, but I’ve noticed that this notification seems to be a bit untrustworthy at the moment.Let’s say you’re a senior and experienced developer and you are lucky enough to get this friendly message in Visual Studio about a file collision. You know exactly which file should be overwritten and which one should be discarded and choose correctly. Would everyone else on your team know to make the same decision? Furthermore, if the development cycle lasts more than 3 months, chances are updates will be published for previously installed packages. This collision will continue to manifest itself for the life of your project every time you decide to install updates.
NuGet environmental remediation
I think there are easy solutions to remedy what’s going on with NuGet. The implementation and transition may not be so easy, but definitely easy to list out here. :)Responsible Party | Remedy |
---|---|
NuGet team | Prevent access to the root folders of Scripts and Content for file deliverables and force all content into package subfolders in the application project |
Package author | Publish a new version of your client-side package with an edited nuspec that places all files into a subfolder (ideally your package name) |
Project team | Continue using NuGet but delete your Scripts and Content folder (or exclude it from your project). Copy the files that you need (manually or automatically) into a different project folder so it’s clear for all team members that NuGet is not used for file delivery. |
Recommendations
- Use Bower for JavaScript or CSS packages instead of NuGet.
- Create a directory that mirrors the structure of bower_components and copy over only the artifacts you’re interested in using.
Don’t actually copy the bower_components folder into your csproj, though, as this folder can contains thousands of files. This can cause Visual Studio to consume too much memory (ReSharper, anyone?) and will generally slow things down as it’s frantically trying to update your csproj file.
This recommendation should make it really easy to know when to use NuGet and when to use Bower, and simple is good.
Technology | Package Manager |
---|---|
.NET | NuGet |
JavaScript | Bower |
CSS | Bower |
No comments:
Post a Comment