ASP.NET Core 2.0 and the End of Bower


Bower is still being maintained, but they're recommending that people move their projects to Yarn and Webpack. As you may not know, I'm on a sort of campaign to avoid the complexity of something like Webpack until you really need it.

In addition, some libraries aren't supported by Bower (e.g. Angular 2-5) so I wanted to finally end my use of two package managers when I needed Angular. My decision has been to use NPM instead of Bower since that's where Angular lives at and is a huge ecosystem thanks to node.

UPDATE: Seems that Yarn isn't tied to Webpack like I thought. Sorry for the confusion. I've removed that from the article and will have a new article on Yarn soon.

In ASP.NET Core, Bower has always been easy. This is mainly because you can configure Bower to store the libraries directly in the /wwwroot directory so it's easy to start a project that way. Ultimately the goal was always to move away from that approach and use something like Gulp to copy just the distribution files (instead of the entire package) to reduce the size of my deployments. But since it was working, I rarely fixed it. I decided to bite the bullet and do it for my WilderBlog open source project.

First Things First

Before I show you what I came up with, you might be asked why not Yarn and Webpack? The main reason I am opposed to Yarn is that it seems from early testing that it's pretty tied to using Webpack. Webpack on it's own isn't bad, but Webpack for simple, straightforward web usage seems like an unneeded complexity. Merging known libraries into Webpack means you have to debug all code, not just your code. You also defeat browser caching by bundling the code together. I know there are benefits of tree-shaking, but for my needs - simplicity is king. I use Webpack, just not in every single case. Now that you have the ammunition to yell at me in the comments, let's move on.

Getting Client-side Dependencies

Node Package Manager (e.g. NPM) is a simple package manager that has been used to manage dependencies for server-side JavaScript. But since it was created, more and more client-side projects have stored their dependencies in there too. If you're an ASP.NET stalwart and haven't used NPM before, you can think of NPM as similar to Nuget.

You can add a new package.json file to your ASP.NET Core project by adding a new item (Ctrl-Shift-A) and picking the npm Configuration file:

npm config file

Alternatively, if you're comfortable with the command-line, you can simply open a console and type:

c:/>npm init

This will create prompt you for info on the project (just accept the defaults for anything you're not familiar with). Either way, you'll end up with a new package.json file in the root of your project.

You can add dependencies directly by editing the file and add a new line in the dependencies section (and you'll get intellisense for the packages and the versions):

package.json

You can also add your packages with the command-line by using npm install. You'd specify the name of the package and --save to tell it to add it to the packages.json file automatically (and pick the latest version of the package by default):

c:>npm install jquery --save

At this point you'll end up with a directory called node_modules that contains all the packages (you'll get other packages if the packages you specified require other packages).

Accessing NPM Dependencies from MVC Views

In ASP.NET Core, the runtime supports a piece of middleware called StaticFiles that allows anything in the /wwwroot folder to be accessible from the browser. But since the node_modules directory is outside of /wwwroot, you need to figure out a way to actually refer to the files on your views. I'll present two ways, a quick and temporary solution so you can get started coding a new project; and a better solution that requires more effort on your part.

Quick and Temporary Solution

If you're starting a project quickly and just want to get access to the node_modules directory, you can use the StaticFiles middleware to make this happen:

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

You can see here that I'm adding a second AddStaticFiles middleware to allow you access the node_modules directory as /vendor. The first UseStaticFiles is still needed to support /wwwroot. This way if you want to use the files from the npm dependency, you can simply use /vendor as the prefix:

<link rel="stylesheet" href="~/vendor/bootstrap/dist/css/bootstrap.css" />

<script src="~/vendor/jquery/dist/jquery.js"></script>
<script src="~/vendor/bootstrap/dist/js/bootstrap.js"></script>

This is a temporary solution, but will get you going without having to write a complete Gulp script. But when you're ready, let's look at a more permanent solution. This will not work if you use ASP.NET Core deployment without deploying the entire node_modules folder (which you don't want to support).

A Better Solution

My solution to fix this correctly was to copy only the files that I really needed. The problem is that the dependency that npm installs, includes lots of files (usually everything required to actually build the project), and that is too much. For example, for jquery you get all these files:

You can see here that the only files we really need are in the dist folder. To copy only these files, I use Gulp to do it. I won't give you a complete walk through of adding Gulp to your project, but I will talk about how I'm copying these files.

Assuming you have Gulp setup, what I want to do is use Gulp to find each of the directories for the ones I cared about. I did this by just creating a simple structure for all the libraries:

// Dependency Dirs
var deps = {
  "jquery": {
    "dist/*": ""
  },
  "bootstrap": {
    "dist/**/*": ""
  },
  // ...
    
};

Notice that I specify a name of the folder (jquery), and a name/value pair for the file filter and destination for the found files (empty string is to just put them in the root). Then I can create a simple gulp task that uses this to copy all the files:

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

});

So I just go through each of the objects in deps and look at each property (in case there is more than one directory that is important), and then just use gulp.src and gulp.dest to copy the files to the wwwroot/vendor folder. The reason I'm using merge (another dependency for gulp) is to merge the streams so that I'm returning all streams for Gulp to handle in series.

You can see a full working version in my WilderBlog project:

gulpfile.js

How do you do it in your project?




Application Name WilderBlog Environment Name Production
Application Ver 2.0.0.0 Runtime Framework .NETCoreApp,Version=v2.0
App Path D:\home\site\wwwroot\ Runtime Version .NET Core 4.6.25815.02
Operating System Microsoft Windows 6.2.9200 Runtime Arch X86