Мы продолжаем нашу колонку по теме ASP.NET 5 публикацией от Дмитрия Сикорского ( DmitrySikorsky) — разработчика из Украины. В этой статье Дмитрий подробнее рассказывает о сценариях применения с ASP.NET 5 популярного средства Gulp. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев
До появления ASP.NET 5 я никогда не использовал такие инструменты, как Gulp, поэтому пришлось уделить некоторое время и разобраться, что же это такое, когда я создал свой первый проект на этой платформе (правда, тогда там еще был Grunt, но это не важно). Не стану вдаваться в базовые вещи, которые уже и так везде достаточно подробно описаны (подразумеваю, что в вашем проекте уже есть Gulpfile.js и вы можете выполнять задания из него, используя диспетчер выполнения задач Visual Studio 2015), а сразу перейду к делу и на практике покажу, как можно использовать Gulp для автоматизации всего-всего в вашем проекте на ASP.NET 5.


В статье будут приведены фрагменты файла Gulpfile.js тестового проекта AspNet5Gulpization, который целиком лежит тут: https://github.com/DmitrySikorsky/AspNet5Gulpization.

Вступление


Вы наверняка знаете, для чего используется новая папка wwwroot. На самом деле, с ее появлением я немного по-новому взглянул на скрипты, стили и картинки. А именно, как и серверный код сайта, теперь я разделяю их на исходники и готовые к публикации объекты.

Подготовка

aspnetcolumngithubСовет! Вы можете попробовать все самостоятельно или загрузив исходный код из GitHub https://github.com/DmitrySikorsky/AspNet5Gulpization.
Для начала, нам необходимо перечислить в нашем Gulp-файле все пакеты, которые мы будем использовать в своих заданиях (и убедиться, что все они присутствуют в package.json):

var gulp = require("gulp"),
  autoprefixer = require("gulp-autoprefixer"),
  concat = require("gulp-concat"),
  del = require("del"),
  minifyCss = require("gulp-minify-css"),
  rename = require("gulp-rename"),
  runSequence = require("run-sequence"),
  sass = require("gulp-sass"),
  tsc = require("gulp-tsc"),
  uglify = require("gulp-uglify");

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

var paths = {
  frontend: {
    scss: {
      src: [
        "styles/*.scss"
      ],
      dest: "wwwroot/css"
    },
    ts: {
      src: [
        "scripts/*.ts"
      ],
      dest: "wwwroot/js"
    }
  },
  shared: {
    bower: {
      src: "bower_components",
      dest: "wwwroot/lib"
    }
  }
}

И наконец, опишем основную Gulp-задачу, которая будет производить перестроение всех скриптов и стилей, их обработку и копирование в результирующую папку:

gulp.task(
  "rebuild",
  function (cb) {
    runSequence(
      "clean",
      "build",
      "minify",
      "delete-unminified",
      "rename-temp-minified",
      "delete-temp-minified",
      cb
    );
  }
);

Пакет runSequence позволяет выполнять задачи одну за другой, последовательно, что в нашем случае очень важно. Также, следует обязательно передавать функцию обратного вызова cb в качестве последней задачи последовательности, чтобы вызывающий код мог быть уведомленным о завершении процесса в целом.

Кстати, чтобы не выполнять задание rebuild вручную, можно либо заставить студию выполнять его при сборке или открытии проекта, либо прямо в Gulp-файле дописать еще одно задание (и выполнять его автоматически при открытии проекта), которое будет устанавливать слушателей на изменение исходных файлов и выполнять задание, когда такое изменение будет обнаружено.

Скрипты


Я очень любил (и продолжаю) JavaScript за его простоту и изящество. Теперь я полюбил еще и TypeScript. (Это замечательный инструмент, рекомендую обратить на него внимание.) Все ts-файлы я обычно храню в папке Scripts, которая игнорируется при публикации проекта. Это исходники клиентских скриптов. Я настроил несколько заданий в моем Gulp-файле, которые сначала компилируют TypeScript в JavaScript, затем сжимает его, затем склеивают в один файл и, наконец, копируют полученный файл в папку wwwroot/js, откуда он и используется в приложении. (Если вы не используете TypeScript, то можно просто пропустить этап его превращения в JavaScript – остальные задания будут работать без изменений.)

По умолчанию, Visual Studio 2015 компилирует ts-файлы в момент их сохранения и складывает полученные js-файлы в ту же папку. Нам это поведение не нужно, поэтому отключаем компиляцию TypeScript в настройках проекта.

Вот так выглядит задание, компилирующее TypeScript:

gulp.task(
  "frontend:build-ts", function (cb) {
    return gulp.src(paths.frontend.ts.src)
      .pipe(tsc())
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);

А вот так можно сжать полученный JavaScript и склеить его в один результирующий файл:

gulp.task(
  "frontend:minify-js", function (cb) {
    return gulp.src(paths.frontend.ts.dest + "/*.js")
      .pipe(uglify())
      .pipe(concat("aspnet5gulpization.min.js.temp"))
      .pipe(gulp.dest(paths.frontend.ts.dest));
  }
);

При склейке многих js-файлов в один возможна ситуация, когда они будут добавлены в результирующий файл в неправильном порядке и вы получите сообщения, что что-то не определено, т. к. определение будет расположено после вызова. Если так случится, можно легко задать порядок следования скриптов в результирующем файле вручную. Можно даже перечислить в необходимом порядке те скрипты, которые должны следовать первыми, а затем последним элементом массива просто указать ту же самую папку, и Gulp догадается, что нужно сначала обработать явно указанные файлы из папки, а затем все остальные.

Кстати, если вдруг при восстановлении пакетов NPM вы получаете сообщение об ошибке (опционально, с формулировкой, состоящей из бессвязной последовательности символов), или же Visual Studio 2015 просто падает при попытке восстановить пакеты, возможно это связано с глубиной вложенности вашего проекта в файловой системе. (Частично, я нашел информацию об этом тут: https://github.com/Microsoft/nodejstools/issues/336.) Потратив некоторое время, я просто создал пустой проект в менее вложенной папке, скопировал туда свой package.json, восстановил там пакеты и затем перенес их вместе с папкой node_modules в свой проект. Также, в процессе падения студии в папку npm-cache может попасть испорченный пакет, поэтому стоит иметь это в виду и при необходимости его оттуда удалить.

Стили


Относительно недавно я решил попробовать SCSS. Основной целью было иметь возможность редактировать такие вещи, как цвета, в одном месте, а не с использованием поиска и замены. Также я решил, что было бы здорово, заодно, разделять огромные css-файлы на части, чтобы их было удобнее сопровождать (на мой взгляд, сопровождать стили, избегая при этом их замусоривания, достаточно сложно, т. к. результат изменений сложно поддается тестированию и не всегда очевиден с первого взгляда). Использование SCSS дало хороший результат.

Аналогично TypeScript, мой Gulp-файл содержит задания для компиляции SCSS в CSS, добавления вендорных префиксов, сжатия, склейки и копирования полученного файла в папку wwwroot/css.

Компиляция SCSS в CSS выглядит следующим образом:

gulp.task(
  "frontend:build-scss", function (cb) {
    return gulp.src(paths.frontend.scss.src)
      .pipe(sass())
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);

Ну и сжатие, склейка (с одновременной расстановкой вендорных префиксов):

gulp.task(
  "frontend:minify-css", function (cb) {
    return gulp.src(paths.frontend.scss.dest + "/*.css")
      .pipe(minifyCss())
      .pipe(autoprefixer("last 2 version", "safari 5", "ie 8", "ie 9"))
      .pipe(concat("aspnet5gulpization.min.css.temp"))
      .pipe(gulp.dest(paths.frontend.scss.dest));
  }
);

Библиотеки


Если в проекте на ASP.NET 5 вам потребуется, например, JQuery, скорее всего вы загрузите его при помощи Bower, и, в отличии от NuGet, который использовался ранее, вы получите немного больше, чем просто файл jquery.min.js и парочки других. В папке bower_components будет создана папка jquery, в которой, кроме упомянутого выше файла, будет несжатый вариант библиотеки, а также ее исходники (которые, само собой, при публикации будут игнорироваться).

Если задуматься, мы можем использовать эти библиотеки как минимум двумя способами.

Во-первых, можно просто подключить на страницу файл jquery.min.js, предварительно скопировав его в папку wwwroot/lib/jquery. Так поступил я (не знаю, возможно правильнее использовать сервисы, вроде Google Hosted Libraries, чтобы в некоторых случаях браузер брал закешированный при посещении другого сайта вариант библиотеки, а не скачивал ее вновь, но чаще всего я так не делаю).

Во-вторых, можно взять несжатый вариант библиотеки (jquery.js) и обработать его таким же образом, каким обрабатываются другие скрипты в приложении. Т. е. в результате добавить его в единственный общий js-файл, таким образом уменьшив количество запросов к веб-серверу.

Вот задание, копирующее необходимые файлы для трех библиотек:

gulp.task(
  "lib-copy", function (cb) {
    var lib = {
      "/jquery": "/jquery/dist/jquery*.{js,map}",
      "/jquery-validation": "/jquery-validation/dist/jquery.validate*.js",
      "/jquery-validation-unobtrusive": "/jquery-validation-unobtrusive/jquery.validate.unobtrusive*.js"
    };

    for (var $package in lib) {
      gulp
        .src(paths.shared.bower.src + lib[$package])
        .pipe(gulp.dest(paths.shared.bower.dest + $package));
    }

    cb();
  }
);

Выводы


Мне нравится то, что в итоге получилось. Мне нравится, что можно легко и удобно работать со стилями и скриптами и так же легко и удобно оптимизировать их перед публикацией, обладая полным контролем над процессом. Это, конечно, не единственное, в чем может помочь Gulp, но думаю этого достаточно, чтобы полностью вникнуть в его возможности.

Авторам


Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

Об авторе


Дмитрий Сикорский,
Украина
DmitrySikorsky

Комментарии (13)


  1. musuk
    19.12.2015 08:31

    Правильно ли я понимаю, что Visual Studio 2015 подхватывает Task'и только из одного gulp-файла на весь Solution?
    Вот, скажем, если у меня в солюшене несколько ASP.NET проектов и в каждом по gulp-файлу, то как их засунуть в Task Runner?

    image


    1. DmitrySikorsky
      19.12.2015 11:16
      +1

      Task Runner Explorer в выпадающем списке отображает все проекты, где есть gulpfile.js, и позволяет переключаться между ними в любой момент. Т. е. вы сможете выполнять задания из всех своих gulp-файлов.


      1. musuk
        20.12.2015 22:32

        Ну если вы это видели в живую, то ок. У меня просто так и не вышло скормить студии больше одного gulp-файла.


        1. DmitrySikorsky
          21.12.2015 01:17

          Не только видел, но и использовал в собственном решении. Безо всяких дополнительных действий или сложностей. В моем решении все проекты были физически размещены на одном уровне в файловой системе, а в решении находились в различных папках. Может быть это имеет значение.


  1. Taras_Kovalenko
    19.12.2015 10:48

    Мне кажется если в солюшене выбрать проект ка запускающий (set as startup project) то галп подхватит текущий галп файл.


  1. lostmsu
    19.12.2015 19:06
    -7

    Хоть бы преимущества этой жуйни перед msbuild'ом написали. А то зачем-то переводят всех на новую технологию (которая, кстати, в студии пока весьма криво работает), и никому не говорят зачем. Притом технология на JavaScript. Фу.


    1. zorro1211
      19.12.2015 20:47
      +5

      Вас никто не заставляет переходить, живите с msbuild'ом и будьте счастливы.
      К сожалению столкнулся на работе с подобным вашему отношением к javascript и различным технологиям на его основе. На ваше и подобным вам горе или счастье пока c# в браузере не выполняется а .net надстроки вокруг javascript весьма сильно устарели, так как многим бекэнд разрабочтикам не понять нужд фронтэнда и поэтому у них нет возможности, знаний и желания сделать что-то действительно удобное и полезное. Также нету желания совершенствовать существующие инструмены для фронтэнда (кстати не у всех в .net мире такое же представление как у вас, вот взять например автора этой статьи). Также для расширения инструментов в .net уже у фронтэнд разрабочиков нету знаний и желания разбираться как в .net коде что-то подкрутить. Вот поэтому с приходом node.js у многих разработчиков знающих js появилась возможность поправить положение и судя по ежедневным статьям о различных технологиях (например упомянутой gulp) и инструментах на базе js, дела идут очень успешно, так что это win-win для всех. Вы будете фокусироваться на бекэнд разработке и использовать инструменты которые вам удобны, а фронтэнд разработчики позаботятся о своей части.


      1. kekekeks
        20.12.2015 12:53
        +1

        Вообще говоря заставляют, без project.json достаточно проблемно использовать vNext.


  1. artemmalko
    20.12.2015 07:35
    +3

    Чтобы избавиться от проблем с глубокой вложенностью в node_modules на Windows можно использовать npm 3 версии. Он все пакеты ставит в один node_modules, а не в каждый пакет отдельно.


  1. dshster
    20.12.2015 11:39
    +1

    Дотнетчики знакомятся с Галп, называя новой технологией. А фронтендеры уже отказываются от Галп по причине негибкости и большого количества ручного труда на сложных проектах в пользу Webpack, который в отличии от Галп встраивается в проект и следит за всеми зависимостями.


    1. alemiks
      20.12.2015 12:08

      ждём через пару лет анонс поддержки в студии «новой технологии» webpack


      1. XaocCPS
        20.12.2015 13:59
        +2

        Ждать не нужно, все уже есть visualstudiogallery.msdn.microsoft.com/5497fd10-b1ba-474c-8991-1438ae47012a с наступающими вас, улыбайтесь


    1. baka_cirno
      20.12.2015 22:42
      +1

      Для меня это странно звучит. Эти инструменты перекрывают функционал друг друга где-то на 80%, но 20% все равно остается. Где-то нужно просто забандлить все в один файл, а где-то приходится манипулировать кучей ресурсов, чтобы в итоге построить подходящий билд.
      У меня везде Webpack вызывается внутри таска в Gulp, наиболее удобный вариант для меня.