Подключаем Grunt и Bower к проекту ASP.NET MVC

ASP.NET предлагает хорошую инфраструктуру для серверного кода, но веб-приложения - это ещё и фронтенд. Клиентский код нужно процессить, упаковывать, минифицировать, разбивать на пакеты и т.п. В мире .NET популярен так же Nuget, который содержит множество полезных пакетов. Но в JavaScript мире эту роль выполняет Bower. Поэтому давайте попробуем подключить Grunt (для автоматизации работы с клиентским кодом) и Bower (в качестве пакетного менеджера для JavaScript).

Подключение к проекту ASP.NET MVC

К сожалению, ASP.NET MVC не имеет встроенной поддержки Grunt и Bower. Поэтому для их корректной работы предварительно необходимо установить несколько nuget-пакетов:

Install-Package Npm
Install-Package Grunt
Install-Package Bower

Эти пакеты подтянут остальные зависимости, необходимые для работы. В результате установки пакетов, в папке проекта появится папка .bin - не удаляйте её. Эта папка содержит файлы для запуска Grunt и Bower.

Этого уже достаточно, чтобы запускать grunt и bower в ручном режиме - для этого необходимо запускать соответствующие файлы из папки .bin.

Если требуется делать то же самое при сборке проекта, то необходимо немного модифицировать файл проекта - в самый конец файла .csproj дописываем директивы для запуска npm, grunt и bower:

  <Target Name="NpmBuild" BeforeTargets="Build">
    <Exec Command=".bin\npm install" />
  </Target>
  <Target Name="BowerInstall" AfterTargets="NpmBuild">
    <Exec Command=".bin\bower install" />
  </Target>
  <Target Name="GruntBuild" AfterTargets="BowerInstall">
    <Exec Command=".bin\grunt" />
  </Target>
</Project>

Теперь при сборке ASP.NET MVC проекта будут также запускаться инструменты для сборки клиентского кода.

Инициализация файлов

Для работы Grunt и Bower требуется создать несколько файлов - package.json и bower.json, а так же сам скрипт для сборки - gruntfile.js.

Необходимо открыть консоль Windows и перейти в папку проекта. После этого последовательно запускаем:

npm init

После этого нужно будет ответить на несколько вопросов в интерактивном режиме (имя проекта, версия и т.п.). На этом шаге будет создан файл package.json.

bower init

Все то же самое, но результате создается файл bower.json. При установке пакетов из Bower они будут попадать в папку bower_components. Если вас это не устраивает, то можно добавить в проект файл с именем .bowerrc и переопределить это поведение:

{
  "directory": "Scripts/.bower_components"
}

И последнее - нужно добавить в папку проекта файл gruntfile.js - это скрипт, который будет выполняться при запуске grunt. Пустой скрипт может выглядеть так:

module.exports = function (grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json")
  });

  grunt.registerTask("default", []);

};

Более подробно о содержимом этого файла можно почитать на официальном сайте Grunt.

Подключение пакетов

Теперь, когда grunt и bower установлены и подключены к проекту, самое время попробовать установить пакеты из общего репозитория.

Установка пакетов - это прописывание соответствующих директив в package.json/bower.json с последующим запуском этих инструментов. Поэтому существует два способа подключения пакетов - вручную добавить их в указанные файлы, либо воспользоваться инструментами и подключить их из командной строки. Попробуем сделать это.

Подключим, для начала, пакет grunt-contrib-clean. Это пакет, который занимается очисткой папок перед сборкой. В командной строке выполняем

npm install grunt-contrib-clean --save-dev

Если заглянуть теперь в package.json, то будет видно, что туда добавился новый элемент:

"devDependencies":
{
  "grunt": "^0.4.5",
  "grunt-contrib-clean": "^0.6.0"
}

Кроме того, в папке проекта появилась папка node_modules, которая содержит код этого пакета. На практике эти папки не распространяются с исходным кодом. Т.е. мы можем спокойно удалить папку node_modules и передать код коллеге. Все что нужно сделать в этом случае, на новом месте запустить:

npm install

При запуске этой команды Npm создаст папку node_modules и установит туда все зависимости, которые сейчас находятся в файл package.json. Поскольку мы добавили соответствующие директивы в файл проекта .csproj, запуск этой команды вручную не потребуется - все зависимости будут загружены автоматически при сборке проекта.

Похожа ситуация и с Bower. Подключение пакета:

bower install -S jquery

В bower.json добавляется при этом нужна зависимость:

"dependencies":
{
  "jquery": "~2.1.1",
}

Распространять вместе с проектом папку bower_components тоже не обязательно. Для того, чтобы скачать все подключенные пакеты достаточно запустить следующую команду:

bower install

Так же как и для Npm, пакеты Bower будут подтягиваться автоматически при сборке проекта, поэтому запускать эту команду вручную необязательно.

Как видно, логика подключения пакетов из Npm и Bower очень похожа на Nuget. Напоследок, предлагаю посмотреть как при помощи этих инструментов решаются практические задачи.

Минификация кода

Предположим у нас есть JavaScript-код, который требуется упаковать в один файл и минифицировать. Для каждой задачи по автоматизации нужно найти подходящий пакет в официальном репозитории. В нашем случае подойдет пакет grunt-contrib-uglify.

Подключаем пакет к проекту:

npm install grunt-contrib-uglify --save-dev

Добавляем в скрипт gruntfile.js:

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json"),

    uglify: {
      "WebApp27": {
        files: {
          "dist/result.js": [
            "Scripts/controller1.js",
            "Scripts/controller2.js"]
        }
      }
    }
  });

Смысл скрипта заключается в том, что мы берем файлы controller1.js и controller2.js, минифицируем их, а результат складываем в result.js.

Когда это готово, вызовом метода loadNpmTasks загружаем пакет Uglify при сборке и запускаем его вместе с остальными задачами:

module.exports = function (grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json"),

    uglify: {
      "WebApp27": {
        files: {
          "dist/result.js": [
            "Scripts/controller1.js",
            "Scripts/controller2.js"]
        }
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-uglify");

  grunt.registerTask("default", ["uglify"]);
};

Теперь при запуске grunt из командной строки или при сборке ASP.NET проекта, задача по минификации будет также запущена и файл result.js будет содержать минифицированный вариант.

Подробнее о синтаксисе этого скрипта можно почитать на официальном сайте Grunt.

Сборка LESS

Другой задачей, часто решаемой при помощи Grunt является сборка LESS-файлов. Аналогично предыдущему примеру установим пакет grunt-contrib-less и добавим его директиву в gruntfile.js:

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json"),

    // ...

    less: {
      styles: {
        files: [
        {
          "dist/styles.css": "styles/styles.less"
        }
        ]
      }

    }
  });

  // ...
  grunt.loadNpmTasks("grunt-contrib-less");

  grunt.registerTask("default", [/*...*/, "less"]);
};

Добавим файл styles/styles.less в проект:

@color1: #fff;

body {
  background-color: @color1;
}

И запустим сборку. В итоге в файле dist/styles.css будет содержаться результат компиляции LESS-файла:

body {
  background-color: #ffffff;
}