Updated Nov 23 2020 :: by Alex Yumashev

Developing an ASP.NET app (Core or Framework/MVC) still requires working with external web tools sometimes - for example, when you want to use advanced web-dev stuff like SASS/LESS, PostCSS, or modern frameworks like Tailwind CSS, or just simply being able to minify, bundle and "babelize" your JavaScript

Folks from the .NET camp are not that familiar with the npm world and usually turned to "VS extensions" for these tasks. Like "Web Essentials" or "Web Compiler" or similar.

The problem with that - those web extensions become abandoned, buggy, or even incompatible with recent versions of Visual Studio. So at some point .NET developers realize they have to turn to "native" web package management and task runners. And to be frank, ASP.NET folks (including myself not so long ago) are not that experienced with the modern web/devops stack and it can be overwhelming to learn so many new things at the same time. That is why I decided to write this.

It all started with an abandoned extension

Web Compiler used to be a great tool for Visual Studio. The only problem - it is now abandoned, even though it's been created inside Microsoft. JS minification is not always working, builds are failing, file mapping is buggy, etc. As of today, the repo contains 209 unfixed issues.

But we still need to minify JS and compile our CSS from LESS, do we? Turns out, most of the alternative solutions - "WebPack Runner" extension, "NPM Task Runner", "Bundler and Minifier", "Web Essentials", etc - are also abandoned. They were all created by the same guy - Mads Kristensen - who works for Microsoft and created many great things, but MS clearly has other plans for him these days.

How about native VS without any extensions?

Thing is, Visual Studio - even the free "Community Edition" - comes with built-in support for npm, and a built-in "Task Runner Explorer" tool (that used to be an extension too), and this runner supports Grunt and Gulp out of the box.

Which means we can configure a project in a way that everything works in a "naked" fashion without any external tools.

Step-by-step example: minifying JS automatically

  1. First important step is to enable this setting in Visual Studio:

    This will ensure that NPM-modules are updated automatically every time you open the solution or edit package.json file, similar to what Nuget does in the background. So we don't have to run npm install CLI every time we add/update something.
  2. Create a package.json file in the project root. You can do this via VS menu item: "Add - New item - npm configuration file" menu (type "npm" into the search box for faster navigation):

    This file is the starting point for NPM - it lists all the node modules you'll be running. Similar to packages.config in Nuget.
  3. In package.json add "grunt", "grunt-contrib-uglify" and "grunt-contrib-watch" packages (or go with Gulp if you prefer). The nice thing is that VS will kindly suggest package names and versions as you type:

    This is what you should get:

    {
     "version""1.0.0",
     "name""asp.net",
     "private"true,
     "devDependencies": {
      "grunt""*",
      "grunt-contrib-uglify""*",
      "grunt-contrib-watch""*"
     }
    }

    You've jsut installed three modules: Grunt plus the two plugins - "minifier" and "watcher". The former minifies a file, the latter "wacthes" a file for changes.

    Once you save that file, Visual Studio will automatically download and install all npm packages and all their dependencies in the background. It all goes into the "/node_modules" project subdirectory, so make sure it's excluded in your .gitignore file ;)

  4. Now that Grunt is installed it's time to configure it. Create Gruntfile.js via "Add - New item - Grunt config file"

    Visual Studio will create a default Gruntfile with no tasks. Add the following lines after "initConfig":

    /// <binding ProjectOpened='watch:tasks' />
    /*
    This file in the main entry point for defining grunt tasks and using grunt plugins.
    Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409
    */
    module.exports = function (grunt) {
        grunt.initConfig({
            uglify: { //minify task
                my_target: {
                    files: {
                        'js/destination.min.js': 'js/source.js'
                    }
                }
            },
            watch: { //watching these files for changes
                files: ['js/source.js'],
                tasks: ['uglify']
            }
        });

        grunt.loadNpmTasks('grunt-contrib-uglify');
        grunt.loadNpmTasks('grunt-contrib-watch');
    };
  5. Open "View - Other Windows - Task Runner Explorer" and make sure the Grunt task is there. Hit the "refresh" icon if it's not. Try to run the "uglify" task and make sure it has created the minified JS file.

    Then right-click the "watch" task and bind it to the "Project Open" event. This way every time you open the project - Grunt will start "watching" the files you specified in yout Gruntfile.

  6. That's it! The great thing about this "set and forget" setup, is that all your teammates will get this config after doing a simple git pull and everything will just work.

Hope the code snippets are clear without any explanations. This works for both ASP.NET MVC 5 and ASP.NET Core MVC. For further reading check this MS doc.

P.S. Why use Grunt or Gulp and not just npm "scripts"?

Npm has a built-in task runner, that goes into the "scripts" section of "package.json" and a lot of people consider this the best practice today, instead of using Grunt/Gulp. But there's no way to run those script without installing the "NPM Task Runner" extension that is also kinda abandoned. My challenge here was to use plain Visual Studio without any plugins.


'Building and minifying JS, CSS, LESS, SASS in Visual Studio with no extensions' was written by Alex Yumashev
Alex Yumashev
Alex has founded Jitbit in 2005 and is a software engineer passionate about customer support.


Subscribe comments