Last Updated: 9/26/2015

Occasionally, a modern web developer will encounter the need or the desire to install an NPM module globally on his or her machine. This may become a more common need amongst the ASP.NET development community, especially when considering the release of ASP.NET 5 and its embracing of Node.js with NPM. The increasingly popular Webpack module is one great example of where this approach is recommended. For those unfamiliar with this global module installation process, think of it conceptually as registering a DLL in the Global Assembly Cache (GAC) with the gacutil.exe tool. In this example, the DLL is equivalent to an NPM module such as Webpack; and, the GAC is equivalent to a Windows user’s roaming profile, by default. As an alternative or a supplement to the roaming profile, that location could be Visual Studio’s installation directory, which by the way, the IDE scans to discover external web tooling.

In the enterprise development world, this installation process is cumbersome. This is especially true if the installation needs to be replicated across 20+ other developer’s machines. For the sake of efficiency, it’s best to automate that process through some sort of scripting. The team’s application architect should identify an effective strategy for tackling this and automate it. Very recently, I had a strong use case for this type of automation. The project team was new to NPM, and it was obviously most important to focus on solving the business problem. Project setup has always been one of those cumbersome tasks for me. If there’s a way to ease the pain and to save time, rest assured that I’ll give it a try.

What’s one way to accomplish this in an ASP.NET 5 project using Visual Studio 2015? Thankfully, the IDE ships with Task Runner Explorer, which serves as a robust launch pad for solving this problem. What this means for an ASP.NET developer is that the command line is completely avoidable. Unfortunately, Task Runner Explorer alone won’t suffice for the task at hand. Two additional components are required:

  1. NPM Scripts Task Runner
  2. package-script NPM module

What’s the purpose of these two components?

Overview: NPM Scripts Task Runner

This extension was created by Microsoft’s own Visual Studio web tooling genius, Mads Kristensen. It expands the capabilities of Task Runner Explorer such that any scripts defined in the project’s local package.json file are listed below a new package.json node in Task Runner Explorer. These can be custom scripts or one of several predefined hooks available in NPM. As is the case with custom Grunt or Gulp tasks, these scripts can be launched from Task Runner Explorer. Additionally, the scripts can be attached to any of the four following events:

  1. After Build
  2. Before Build
  3. Clean
  4. Project Open

Overview: package-script NPM Module

For the purposes of this walk through, package-script will be used to install any system-level NPM modules which don’t already exist on the developer’s machine. This NPM module should be installed in your project. Once installed, the following three functions are made available:

  1. install
  2. spawn
  3. uninstall

The install and uninstall functions are clever enough to detect whether a module exists; the spawn function lacks this level of sophistication. Because of the aforementioned knowledge, the install function will only execute the underlying npm install -g command when absolutely necessary.

Setting It Up

Open your package.json file, and add a new package-script key-value pair as a development dependency:

"devDependencies": {
  "package-script": "0.0.8"
}

Next, create a new installer.js file at the project’s root:


var pkgScript = require('package-script');
pkgScript.install([{
admin: false,
name: 'webpack'
}], {
callback: function () {
console.log('installed');
},
init: {
global: true,
log: true
}
});

view raw

installer.js

hosted with ❤ by GitHub

It’s the responsibility of installer.js to use the package-script module for the system-level Webpack installation. In the case of Visual Studio 2015, the resulting files will be copied to the node directory at $(DevEnvDir)\Extensions\Microsoft\Web Tools\External. On my machine, that $(DevEnvDir) variable expands to the following path: C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE. As an aside, this default path may be altered by navigating to Tools –> Options –> Projects and Solutions –> External Web Tools. The following dialog would appear:

External Web Tools

Next, create a custom script in package.json:

  "scripts": {
    "install_global_deps": "node installer.js"
  }

This script has a name of “install_global_deps”, and its value is the command to be executed. In this case, Node.js is being told to run installer.js. After saving these changes, look at Task Runner Explorer. Notice that the new “install_global_deps” script appears:

Task Runner Explorer

Each time the project opens, there should be a check for the existence of the obligatory system-level NPM modules. To satisfy this requirement, right-click the script name, go to “Bindings”, and click “Project Open”. Notice that the “Bindings” tab of Task Runner Explorer has updated:

Task Runner Explorer Project Open binding

Also notice that the package.json file was modified to include a new “-vs-binding” property:


{
"name": "ASP.NET",
"version": "0.0.0",
"scripts": {
"install_global_deps": "node installer.js"
},
"devDependencies": {
"gulp": "3.8.11",
"gulp-concat": "2.5.2",
"gulp-cssmin": "0.1.7",
"gulp-uglify": "1.2.0",
"package-script": "0.0.8",
"rimraf": "2.2.8"
},
"-vs-binding": { "ProjectOpened": [ "install_global_deps" ] }
}

view raw

package.json

hosted with ❤ by GitHub

As an alternative to creating the bindings via the Task Runner Explorer UI, these bindings can be defined manually within the package.json file. Thankfully, intellisense is provided to make this chore more approachable.

Close this solution in Visual Studio, open it again, and focus in on Task Runner Explorer. Note the output which appears in the console:

Task Runner Explorer Script Output

There are a couple important components to note in this output. As you may recall, the installer.js file included a console.log statement in the install function’s callback. That log statements yields some “installed” text, which appears near the bottom of the console output. Secondly, it’s clear that the npm command was executed, since that output appears here as well.

Right-click the script name in Task Runner Explorer and select “Run”. Our goal here is to determine whether package-script is doing its job of preventing the npm command from executing yet again. After all, we know that Webpack was already successfully installed on this machine. Here’s the output from that manual run:

Task Runner Explorer manual script run

Conclusion

This approach will undoubtedly save you time, particularly in large team environments where system-level NPM modules must be used. If the same module is being used over and over again across projects, then global module installations may make sense. My rule of thumb has been to install locally first and to promote to global on an as-needed basis only. Of course, there are some modules which highly recommend installing globally, in which case this advice takes a back seat. As you can see, there’s a bit of setup required to ease the pain in automating global installations, but it can be achieved.

1 Comment »

Leave a comment